That time LINE ROOKIES migrated LINE SHOPPING to Vue 3
Dive into what sparked the idea and what went into the process of migrating a web application from Vue 2 to Vue 3 from the perspective of a LINE ROOKIE.
Greetings, everyone! Let’s begin with a brief introduction. My name is Dhanabordee Mekintharanggur (Point), a soon-to-be 3rd year engineering student majoring in Information and Communication Engineering.
At the time of writing, I would be in the 8th week of the LINE ROOKIE program (LINE’s fancy term for their internship program✨ ). I joined the Social Commerce’s engineering team as a front-end developer alongside the super kind and helpful P’Pete. And let me tell you, the past few weeks have been nothing but valuable experience working with the lovable and super talented LINE Social Commerce: Unicorn team 🦄.
Introduction
As my first ever internship, I can’t be more excited and thankful for the chance to work together with them in a professional yet friendly environment on such a large-scale project like the LINE SHOPPING platform.
As the saying goes, “Time sure flies when you’re having a great time.” ⏳ However, among the long list of sprint tickets, and those that we worked on, a particular ticket struck me as especially interesting.
P’Pete and I were offered the chance to migrate the entire repository of LINE SHOPPING home from the current Webpack-based implementation Vue.js version 2 to a Vue.js version 3 Vite application.
In this blog, I’ll walk you through how the decision came to be, what the process we need to go through was, what are the obstacles faced along the way, and what are interesting libraries we utilized.
How It All Started (Why Migrate?)
As we were working on our tickets on the LINE SHOPPING frontend repository, a particular issue regarding restoring the page scroll position came to our attention.
To give context, the project uses Vue.js version 2 with Class Components syntax with TypeScript. The code responsible for that scrolling functionality were distributed across the rather long Vue file, numbering close to 700 lines.
The project structure lets the child components emit their events that are then handled by the parent index file. While the standard practices were perfectly in check, that long long file is less than ideal. P’Pete mentioned how he would love to refactor all these lengthy files, and then Vue.js version 3 with Composition API came to our mind.
The Implementation
Say hello to the @vue/composition-api, a plugin for Vue.js 2 that allows you to write using the composition API right in Vue 2!
…
Except, it wasn’t as smooth as it should be
Perhaps, this long list of limitations listed on @vue/composition-api’s npm page could speak for itself.
The next logical (and more exciting) implementation would be to use the actual Composition API which will need Vue.js version 3. Not to mention that with Composition API, the Vue instances (those prefixed with $ wouldn’t really work anymore.
There goes this.$apollo
, this.$route
, etc. And as the list gets longer, we came to a conclusion that we might as well upgrade a couple of things along the way.
Using Vue3 and the Composition API Syntax
Coming from class component syntax, things are bound to be changed… lots of them, actually.
Let’s take a look at typical class-based Vue components
<template>
<div>
{{ message }}
<button v-on:click="increment">+</button>
</div>
</template>
<script lang="ts">
import Vue from 'vue'
import Component from 'vue-class-component'
@Component
export default class Counter extends Vue {
// Component data
count = 0 // Method
increment() {
this.count += 1
} // Computed property
get message() {
return 'The count is ' ' + this.count
}}
</script>
Now, with Composition API, the code becomes the following
<template>
<div>
{{ message }}
<button v-on:click="increment">+</button>
</div>
</template>
<script lang="ts">
import Vue from 'vue'
import { defineComponent, ref } from 'vue'
defineComponent({
setup() {
// Component data
const count = ref(0) // Method
const increment = () => {
count.value += 1
} // Computed property
const message = computed<string>(() =>
'The count is ' ' + this.count
) return { increment, message }
})
</script>
- The
setup
function takes 2 optional arguments,props
andcontext
. We can then return the values to be accessed in the template. - Gone is the
this.
notation. ref
andreactive
enables reactivity anywhere in an application. It takes an argument and returns an object with avalue
property that we can use to mutate and access while retaining reactivity.- Similarly,
computed
properties return a read-only reactive reference to the output of the getter function, which can be accessed using a.value
property. - And a couple of other syntax changes…
With Composition API, we can leverage a hook-like implementation in our components. This enables logic extraction that can be organized by business logic rather than options. Just take a look at this comparison between the OG Options API and the new Composition API on how they handle logic grouping.
The custom hook composable can then be refactored into separate files that can be separately maintained and reused in the future.
To learn more about Vue Composition API, check out the doc.
The Approach
Vite.js
Having upgraded to Vue 3, we can now use Vite.js, a front-end build tool that allows us to leverage a blazing fast development server and performant build.
Justly like the Vue CLI, Vite offers a build tool, web server, and project scaffolding. The interesting part is that Vite has its own blazing fast dev server that uses native ES modules within the browser and won’t bundle the app on the server-side, unlike Vue CLI’s Webpack server. By avoiding the server-side building of the whole application, Vite can take advantage of the more efficient module processing of the browser.
As for building the app, Vite uses Rollup, which is faster as well!
Let’s scaffold our Vite project!
npm init vite@latest my-vue-app — template vue
Vue Router 4
With the new Vue 3, the createApp API introduced changes in how we add plugins to a Vue instance. Similarly, Vue Router 4 has its own createRouter API to work together with Vue 3.
In Vue Router 4, the modes have been abstracted into modules, and you can assign them using the history option.
// main.tsimport { createApp } from "vue"
import { createRouter, createWebHistory } from "vue-router";export default createRouter({
history: createWebHistory(),
routes: [...],
})const app = createApp({})
app.use(router)
app.mount("#app")
Vue Apollo 4
“Vue 3 support is work-in-progress in version 4 of Vue Apollo…”
Well, the ship ain’t sailing without Vue Apollo. Let’s jump into Vue Apollo V4.0 so we can leverage the power of GraphQL in our application. Let’s set it up.
import VueApollo from 'vue-apollo'const apolloProvider = new VueApollo({
defaultClient: yourGraphQLClient,
})...Vue.use(VueApollo)...
Now, to make a query:
const result = await apolloClients.default.query({
// your GraphQL query here
})
Learn more about Vue Apollo 4.
Tailwind CSS
Let’s not forget about style 💄. With the jump from Tailwind 1.9.5 to 2.2.4, we can take advantage of performance optimization and additional flexibility.
npx tailwindcss -o dist/tailwind.css --watch --jit --purge="./src/**/*.html"
This migration utilizes the new high-performance Tailwind CLI, and we can achieve super-fast build times by compiling our CSS in JIT (Just-in-time) mode, which we will be using in our migration.
Additionally, we can not generate arbitrary styles without having to write an additional custom CSS. Suppose you want a specific height down to the pixels, you can take advantage of Tailwind’s new square bracket notation like class=“h-[100px]”
right away!.
To enable JIT mode, simply declare it inside your tailwind config file like so.
// tailwind.config.js
module.exports = {
mode: 'jit',
purge: [
// ...
],
theme: {
// ...
}
// ...
}
Learn more about Tailwind JIT mode.
Obstacles Encountered
The migration wasn’t always a smoothly paved road. Switching syntax left some parts of the code incorrect due to the sheer size of code we had to change syntax.
We have to keep reminding ourselves that if we were to reassign the whole object, use ref
, not reactive
.
let myObj = reactive({
x: 0,
y: 0
})// this works
myObj.x = 1
myObj.y = 1
// this will not work since reassigning the object will cause it to lose reactivitymyObj = {
x: 1,
y: 1
}
Instead, use ref
like so.
let myObj = ref({
x: 0,
y: 0
})
Along the way, we found a few dependencies simply wouldn’t work with Vue 3. We extracted those libraries and include them straight inside the repository
Make adjustments along the way so they would utilize TypeScript and Composition API, and Maintain libraries that do not support Vue3 in the repository itself.
“Hippity hoppity, your library is now our property.”
Overall, just tinkering with the libraries until they work as expected as we apply Composition API syntax and set them up in the Vue 3 project.
The End Result
Visually, it’s as if nothing has changed. Well, they better not!
At the end of the journey, we ended up with a more modular code with business logic categorized together. Composable allows files to be refactored and shortened.
Vite.js Rollup and Tailwind 2.2.4 JIT mode improved the developer experience by significantly reducing build times and performance overhead of the application.
A few libraries were updated to their latest version, requiring syntactical changes.
Should You Migrate Your Vue.js application to Version 3 with Composition API?
Nothing is going to stop you from migrating your Vue 2 application to Vue 3 … save for some gotchas you have to consider.
However, do keep in mind that many of Vue’s beloved libraries are dependent on its community. Some take it time to support Vue 3, while some haven’t come up with Vue 3 support out of the box. Of course, you could look for various other alternatives, but that’s not always ideal. ref
and reactive
can be rather confusing initially. And setup
function does come with a little additional boilerplate.
But at the end of the day with Vue 3, we can expect our application to be a little faster, smaller-sized, and easier to maintain with the new optional Composition API. Large, complex-featured projects where logic gets complex and is reused are prime candidates for the upgrade. We can expect better compatibility with Typescript and access to lower-level features such as reactivity. And finally, we get to get the project up-to-date with the latest industry standards.
Here’s a good article about when you should use the Composition API.