DEV Community

Cover image for How to use AI to migrate a legacy AngularJS app to VueJS without writing (a lot of) code
Matthieu Drouian
Matthieu Drouian

Posted on • Originally published at blog.ziggornif.xyz

How to use AI to migrate a legacy AngularJS app to VueJS without writing (a lot of) code

The JavaScript ecosystem has the particularity of being very versatile. Every year, we see new frameworks and new tools replacing those already in place.

If, like me, you have JavaScript in production, you have probably experienced application rewrites for this reason.

Sometimes, applications are easy to rewrite, and it can be done quickly and easily following the deprecation of a framework.

However, if the scope of the application to be migrated is large... it can take a lot of time and represent a significant cost for the company that will tackle this tedious task.

It can even happen that the time estimates are so high that the company decides to give up before having a good reason to take action (security vulnerabilities, ...).

Ask for an AI!

Today AI types GPT3, GPT4 or even GitHub Copilot have the ability to write code.

Why not use this ability of AI available on the internet to save time?

We will test this idea with a deprecated AngularJS application that I will try to migrate to VueJS throughout this article.

Let’s summarize

  • AngularJS is an old framework, it will necessarily be present in the data of GPT3.5.
  • VueJS has also been present for a few years, ChatGPT should be able to transform AngularJS into VueJS.

The goal is simple: migrate this application by writing as little code as possible.

My migration plan

composition

The application that will be used to do the test can be broken down into four major parts:

  • the components
  • the views
  • services (files used to call the API or do stuff with the browser)
  • the router

My migration plan is as follows:

  • Condition ChatGPT with the first prompt to be a JavaScript expert with strong knowledge of AngularJS and VueJS
  • Init a new VueJS app
  • Migrate components
  • Migrate services
  • Migrate views
  • Migrate router
  • Add application custom style

AI conditioning

I've conditioned my ChatGPT chat with this first prompt :

You are a senior Javascript frontend developper with strong skills in AngularJS and VueJS frameworks.
I need you today to help me migrate an AngularJS application to VueJS
My next messages will be parts of the AngularJS application to migrate.

first prompt

This will help to have relevant results when migrating parts of our application.

Init the VueJS app

The simplest part of the project, we use the Vue CLI to create a new project that will be the target project of the migration.

~/Workspace/angularjs-to-vue3-project 
❯ npm create vue@latest
Need to install the following packages:
  create-vue@3.7.5
Ok to proceed? (y) 

Vue.js - The Progressive JavaScript Framework

✔ Project name: … frontend-target-app
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit Testing? … No / Yes
✔ Add an End-to-End Testing Solution? › No
✔ Add ESLint for code quality? … No / Yes
✔ Add Prettier for code formatting? … No / Yes

Scaffolding project in /home/user/Workspace/angularjs-to-vue3-project/frontend-target-app...

Done. Now run:

  cd frontend-target-app
  npm install
  npm run format
  npm run dev

Enter fullscreen mode Exit fullscreen mode

Migrate a component

The AI is now ready, let's try to migrate our first component.

Request

component migration

The rest of my message contains the JavaScript code and the html of the AngularJS component

import template from "./sign-form.template.html?raw";

function controller($scope) {
  this.$onInit = () => {
    this.bgImg = {
      "background-image": `url(${this.img})`,
    };
    this.username = "";
    this.password = "";
  };

  this.clearForm = () => {
    this.username = "";
    this.password = "";
  };

  this.handleSubmit = () => {
    $scope.$emit("signSubmited", {
      username: this.username,
      password: this.password,
    });
    this.clearForm();
  };
}

const component = {
  template,
  controller,
  bindings: {
    formTitle: "@",
    btnLabel: "@",
    img: "@",
  },
  transclude: {
    slot: "?slot",
  },
};

component.$inject = ["$scope"];

export default ["signForm", component];

Enter fullscreen mode Exit fullscreen mode
<article class="grid">
  <div>
    <h2>{{$ctrl.formTitle}}</h2>
    <form ng-submit="$ctrl.handleSubmit()">
      <label htmlfor="username">
        Username
        <input type="text" id="username" ng-model="$ctrl.username" required />
      </label>
      <label htmlfor="password">
        Password
        <input type="password" id="password" ng-model="$ctrl.password" required />
      </label>
      <button class="contrast">{{$ctrl.btnLabel}}</button>
      <div ng-transclude="slot"></div>
    </form>
  </div>
  <div class="sign-img" ng-style="$ctrl.bgImg"></div>
</article>
Enter fullscreen mode Exit fullscreen mode

Result

Now let’s see what ChatGPT generated in response.

response

<template>
  <article class="grid">
    <div>
      <h2>{{ formTitle }}</h2>
      <form @submit.prevent="handleSubmit">
        <label for="username">
          Username
          <input v-model="username" type="text" id="username" required />
        </label>
        <label for="password">
          Password
          <input v-model="password" type="password" id="password" required />
        </label>
        <button class="contrast">{{ btnLabel }}</button>
        <div v-if="$slots.slot"><slot name="slot"></slot></div>
      </form>
    </div>
    <div class="sign-img" :style="bgImg"></div>
  </article>
</template>

<script>
export default {
  props: {
    formTitle: String,
    btnLabel: String,
    img: String,
  },
  data() {
    return {
      username: "",
      password: "",
    };
  },
  computed: {
    bgImg() {
      return {
        "background-image": `url(${this.img})`,
      };
    },
  },
  methods: {
    handleSubmit() {
      this.$emit("signSubmited", {
        username: this.username,
        password: this.password,
      });
      this.clearForm();
    },
    clearForm() {
      this.username = "";
      this.password = "";
    },
  },
};
</script>

<style scoped>
/* Add your component-specific styles here */
</style>
Enter fullscreen mode Exit fullscreen mode

And the explanations :

response explain

The result looks great, we now have a component translated into VueJS !

Let's do it with other components.

Migrate a service

After successfully migrating all our components, we can move to the service layer.

Request

service prompt

As for the previous part, the rest of my message contains the JavaScript code of the service.

function accountService($cookies) {
  this.authenticate = (username: string, password: string) => {
    return fetch("http://localhost:8080/signin", {
      method: "POST",
      headers: {
        Accept: "text/plain",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        username,
        password,
      }),
    })
      .then((response) => {
        return response.text();
      })
      .then((token) => {
        $cookies.put("auth-token", token);
      });
  };

  this.register = (username: string, password: string) => {
    return fetch("http://localhost:8080/signup", {
      method: "POST",
      headers: {
        Accept: "text/plain",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        username,
        password,
      }),
    });
  };

  this.getToken = () => {
    return $cookies.get("auth-token");
  };

  this.isAuthenticated = () => {
    const token = this.getToken();
    return token?.length;
  };

  this.getUsername = () => {
    const token = this.getToken();
    const [user] = atob(token).split(":");
    return user;
  };

  this.logout = () => {
    $cookies.remove("auth-token");
  };
}

export default ["accountService", accountService];

Enter fullscreen mode Exit fullscreen mode

Response

As for the components, ChatGPT generates a migrated version of our service.

import axios from 'axios'

export default {
  authenticate(username, password) {
    return axios
      .post('http://localhost:8080/signin', {
        username,
        password
      })
      .then((response) => {
        const token = response.data
        this.setToken(token)
      })
  },

  register(username, password) {
    return axios.post('http://localhost:8080/signup', {
      username,
      password
    })
  },

  setToken(token) {
    localStorage.setItem('auth-token', token)
  },

  getToken() {
    return localStorage.getItem('auth-token')
  },

  isAuthenticated() {
    const token = this.getToken()
    return token && token.length > 0
  },

  getUsername() {
    const token = this.getToken()
    if (token) {
      const user = atob(token).split(':')[0]
      return user
    }
    return null
  },

  logout() {
    localStorage.removeItem('auth-token')
  }
}

Enter fullscreen mode Exit fullscreen mode

This part can be the simplest in some applications, especially if the service layer is written in pure Javascript, no need for migration in this case.

Migrate a view

We apply the same process again. We ask ChatGPT to migrate the view by providing JS code and HTML.

Do not forget to tell the AI to use the components and services generated previously.

Request

Migrate the signin view that use the SignForm.vue component created before :

function signInController($scope, $window, accountService) {
  function authenticate(event, data) {
    accountService.authenticate(data.username, data.password).then(() => {
      $window.location.href = "#/home";
    });
  }

  $scope.$on("signSubmited", authenticate);
}

signInController.$inject = ["$scope", "$window", "accountService"];

export default ["signInController", signInController];

Enter fullscreen mode Exit fullscreen mode
<honk-layout>
  <slot-main>
    <sign-form
      form-title="Sign in"
      btn-label="Login"
      img="https://source.unsplash.com/uymG7UVPXpI/1000x1200"
      username="{{$scope.username}}"
      password="{{$scope.password}}"
    >
      <slot>
        <p class="sign-option">
          Don't have an account yet?
          <a href="#/signup">Register now</a>
        </p>
      </slot>
    </sign-form>
  </slot-main>
</honk-layout>
Enter fullscreen mode Exit fullscreen mode

Response

Here's the Vue.js version of the signin view that uses the SignForm component:

<template>
    <HonkLayout>
        <template #main>
        <SignForm
            formTitle="Sign in"
            btnLabel="Login"
            :img="img"
            :username="username"
            :password="password"
            @signSubmited="authenticate"
        >
            <template v-slot:slot>
            <p class="sign-option">
                Don't have an account yet?
                <a href="#/signup">Register now</a>
            </p>
            </template>
        </SignForm>
        </template>
    </HonkLayout>
</template>

<script>
import HonkLayout from '../components/HonkLayout.vue'; // Import the HonkLayout component
import SignForm from '../components/SignForm.vue'; // Import the SignForm component
import accountService from '../services/account.service'; // Import the accountService

export default {
components: {
    HonkLayout,
    SignForm,
},
data() {
    return {
    img: 'https://source.unsplash.com/uymG7UVPXpI/1000x1200',
    username: '',
    password: '',
    };
},
methods: {
    authenticate(data) {
        const { username, password } = data;
        accountService.authenticate(username, password)
        .then(() => {
            // Redirect to the home page after successful authentication
            this.$router.push('/home');
        });
    },
},
};
</script>

<style scoped>
/* Add your component-specific styles here */
</style>

Enter fullscreen mode Exit fullscreen mode

The generated VueJS component looks correct. Let’s repeat the action for the other views to finish this part.

Migrate the router

The router is a rather special part, it is the file that will manage the "UI" routes of our Single Page Application (SPA).

As for writing components this part will differ depending on the framework used.

Request

After seeing it I think you understood it, we will once again ask ChatGPT to migrate the router by providing the code 😅.

Now write the vue router from the angularjs one

function config($routeProvider, $locationProvider) {
  $locationProvider.hashPrefix("");
  $routeProvider
    .when("/", {
        template: signInTemplate,
        controller: "signInController",
        name: "signin",
    })
    .when("/home", {
      template: homeTemplate,
      controller: "homeController",
      name: "home",
    })
    .when("/signup", {
      template: signUpTemplate,
      controller: "signUpController",
      name: "signup",
    });
}

config.$inject = ["$routeProvider", "$locationProvider"];

export default config;
Enter fullscreen mode Exit fullscreen mode

Response

In my case, ChatGPT was able to generate me an equivalent router in VueJS that works perfectly.

To create a Vue Router configuration based on the AngularJS route configuration you provided, you can define routes and use Vue Router for navigation. Here's the equivalent Vue Router configuration:

import { createRouter, createWebHashHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import SignInView from '../views/SignInView.vue'
import SignUpView from '../views/SignUpView.vue'

const router = createRouter({
  history: createWebHashHistory(),
  routes: [
    {
      path: '/',
      component: SignInView
    },
    {
      path: '/home',
      component: HomeView
    },
    {
      path: '/signup',
      component: SignUpView
    }
  ]
})

export default router
Enter fullscreen mode Exit fullscreen mode

We now have our, components, views, services and router, all that’s missing is the migration of our app style!

Add application style

Now that all the application code is migrated, we can copy the CSS part.

Copy the app.scss from the AngularJS app to the assets Vue app directory.

Add sass dependencies in the project.

npm install --save-dev sass-loader sass

Finally, add the style import in the main.ts file of the application.

import '/posts/app.scss'
Enter fullscreen mode Exit fullscreen mode

The new app is ready, we can proceed to the test.

Let's run the new app

In the case of our test app, we get exactly the same rendering with the new app! 🎉

Let’s look at each screen before and after migration.

Sign in form

AngularJS

form

VueJS

form

Sign up form

AngularJS

form

VueJS

form

Home page

AngularJS

form

VueJS

form

Facts !

Looking at the captures, one would think I just used the same image for both cases. Let’s see what the DOM says.

AngularJS

dom

The application written in AngularJS has the ng-app attribute.

VueJS

dom

The application written in VueJS has the data-v-app attribute.

Help, the code written by the AI is wrong! 😱

Do not hesitate to correct it when answers are wrong or if they are missing parts.

Example :

On the first migration code, the ChatGPT solution was not using the signSubmited event data.

Legacy code :

function authenticate(event, data) {
    accountService.authenticate(data.username, data.password).then(() => {
        $window.location.href = "#/home";
    });
}

$scope.$on("signSubmited", authenticate);
Enter fullscreen mode Exit fullscreen mode

First wrong proposition :

authenticate() {
    accountService.authenticate(this.username, this.password)
    .then(() => {
        this.$router.push('/home');
    });
}
Enter fullscreen mode Exit fullscreen mode

After that, i've say to ChatGPT that he is not using the event data :

you are not using the username and password sent in the signSubmited event

Then he correct it immediately !

You're correct; I apologize for the oversight. To use the username and password sent in the signSubmited event, you should update the authenticate method to receive the data object passed through the event. Here's the corrected signin Vue.js component:

authenticate(data) {
    const { username, password } = data;
    accountService.authenticate(username, password)
    .then(() => {
        this.$router.push('/home');
    });
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

I found the result of this migration test very interesting.

It took me less than two hours to fully migrate my test application.

Regarding the AI part, it is important to keep the same conversation throughout the migration to keep the same context.
This will facilitate the work of the AI when requesting changes to the generated code.

When the AI is wrong or forgets to migrate certain portions, it is important to correct it to keep a correct context.

Project link

https://gitlab.com/ziggornif/angularjs-to-vue3-project

Top comments (0)