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
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.
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
Migrate a component
The AI is now ready, let’s try to migrate our first component.
Request
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];
<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>
Result
Now let’s see what ChatGPT generated in 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>
And the explanations :
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
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];
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')
}
}
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];
<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>
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>
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;
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
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'
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
VueJS
Sign up form
AngularJS
VueJS
Home page
AngularJS
VueJS
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
The application written in AngularJS has the ng-app
attribute.
VueJS
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);
First wrong proposition :
authenticate() {
accountService.authenticate(this.username, this.password)
.then(() => {
this.$router.push('/home');
});
}
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');
});
}
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.