Vue Options to Composition API Online Converter

If you are reading this, chances are you have old projects using Vue2 options API and you want to migrate it to Vue3.

One of the main features that were introduced in vue3 is composition API. It helps writing a code that resembles “regular” JavaScript functions.

While Vue team has provided backward compatibility for options API, it is still a good idea to migrate the code to get the benefit of the new features in Vue3 and have a more maintainable code.

However, many developers choose not to migrate for a multitude of reasons.

  • Migration might introduce new bugs to a rather working code (if the code is not broke don’t fix it).
  • There is a learning curve required when rewrite the code with the new features.
  • Busy schedule. Most developers work on few projects simultaneously and do not have enough time for improving old code.

This is why I developed this online tool. It converts options API to composition API without much effort from the developers.

The tool can convert this code:

// https://github.com/gitlabhq/gitlabhq/blob/e6d048d769240760008f0dbb6b811e1ebc675292/app/assets/javascripts/ide/components/repo_tab.vue#L3

import { GlIcon, GlTab } from '@gitlab/ui';
import { mapActions, mapGetters } from 'vuex';
import { __, sprintf } from '~/locale';

import ChangedFileIcon from '~/vue_shared/components/changed_file_icon.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue';
import FileStatusIcon from './repo_file_status_icon.vue';

export default {
  components: {
    FileStatusIcon,
    FileIcon,
    GlIcon,
    ChangedFileIcon,
    GlTab,
  },
  props: {
    tab: {
      type: Object,
      required: true,
    },
  },
  data() {
    return {
      tabMouseOver: false,
    };
  },
  computed: {
    ...mapGetters(['getUrlForPath']),
    closeLabel() {
      if (this.fileHasChanged) {
        return sprintf(__('%{tabname} changed'), { tabname: this.tab.name });
      }
      return sprintf(__('Close %{tabname}'), { tabname: this.tab.name });
    },
    showChangedIcon() {
      if (this.tab.pending) return true;

      return this.fileHasChanged ? !this.tabMouseOver : false;
    },
    fileHasChanged() {
      return this.tab.changed || this.tab.tempFile || this.tab.staged || this.tab.deleted;
    },
  },

  methods: {
    ...mapActions(['closeFile', 'updateDelayViewerUpdated', 'openPendingTab']),
    clickFile(tab) {
      if (tab.active) return;

      this.updateDelayViewerUpdated(true);

      if (tab.pending) {
        this.openPendingTab({ file: tab, keyPrefix: tab.staged ? 'staged' : 'unstaged' });
      } else {
        this.$router.push(this.getUrlForPath(tab.path));
      }
    },
    mouseOverTab() {
      if (this.fileHasChanged) {
        this.tabMouseOver = true;
      }
    },
    mouseOutTab() {
      if (this.fileHasChanged) {
        this.tabMouseOver = false;
      }
    },
  },
};

To this

import { GlIcon, GlTab } from '@gitlab/ui';
import { mapActions, mapGetters } from 'vuex';
import { __, sprintf } from '~/locale';
import ChangedFileIcon from '~/vue_shared/components/changed_file_icon.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue';
import FileStatusIcon from './repo_file_status_icon.vue';
import { ref, computed } from 'vue';


// Data
const tabMouseOver = ref('false');

// Props
const props = defineProps({
    tab: {
        type: Object,
        required: true,
    },
});

// Computed
const closeLabel = computed(() => {
    if (fileHasChanged.value) {
        return sprintf(__('%{tabname} changed'), { tabname: props.tab.name });
    }
    return sprintf(__('Close %{tabname}'), { tabname: props.tab.name });
})

const showChangedIcon = computed(() => {
    if (props.tab.pending) return true;

    return fileHasChanged.value ? !tabMouseOver.value : false;
})

const fileHasChanged = computed(() => {
    return props.tab.changed || props.tab.tempFile || props.tab.staged || props.tab.deleted;
})


// Methods
const clickFile = function(tab) {
    if (tab.active) return;

    this.updateDelayViewerUpdated(true);

    if (tab.pending) {
        this.openPendingTab({ file: tab,
            keyPrefix: tab.staged ? 'staged' : 'unstaged' });
    } else {
        this.$router.push(this.getUrlForPath(tab.path));
    }
}

const mouseOverTab = function() {
    if (this.fileHasChanged) {
        tabMouseOver.value = true;
    }
}

const mouseOutTab = function() {
    if (this.fileHasChanged) {
        tabMouseOver.value = false;
    }
}

You can see the tool here: https://kalimah-apps.com/vue-options-to-composition

When you use this tool please remember that it does not cover all cases. It is useful to get you a step head and remove one hurdle from the code migrating process.

If you would like to contribute, report a bug or request a feature you can check the repo at GitHub:

https://github.com/kalimahapps/vue-options-to-composition