DEV Community

Cover image for How to deal with a smelly Qing Dynasty project? Refactor it with TypeScript!
Larry Zhu
Larry Zhu

Posted on

How to deal with a smelly Qing Dynasty project? Refactor it with TypeScript!

Multiple Image Warning: Enter with caution if you have limited data.

Project Background

Recently, the company asked us to add new features to an old project, specifically converting the previously free services into paid ones, and adding some additional functionalities. When I saw the project online, I had a bad feeling. Let's take a look at some screenshots:

Farm Digital Cloud Page
Farm Digital Cloud Page

Alarm Platform Page
Alarm Platform Page

Weather Monitoring Page
Weather Monitoring Page (NavBar disappeared, oh my 😅)

Meteorological Big Data Page
Meteorological Big Data Page (Another new project without NavBar)

Not to mention the errors and overwhelming log messages. The code structure and directory organization are also extremely chaotic. It seems like a mishmash of components and views, making it challenging to differentiate between them. Have any of you encountered such an epic mess of directories?

After being shocked by the directory structure, I prepared myself to look at the code. To my surprise, the Vue components are relatively well-written:

Vue Component

(That pagination function was added later.) Well, it's acceptable, and the naming is quite standard. I can read through this in about ten minutes. Let's continue with another component:

Another Vue Component

Alright, this one is also decent, and along with the template, it's understandable. Let's see another one:

One More Vue Component

Wait? It's acceptable to have two functions that look similar, but shouldn't their functionality be similar too? The addControl2 and addControl functions are completely different! While both may qualify as controls, their actual functionalities are not the same at all!

Overall, the Vue components are acceptable, but maintaining a project like this would be extremely tiring (the screenshots are not an exception; every file has similar issues). After working on the changes for a week, I proposed a refactor, and to my surprise, the leadership approved it immediately and handed the task to me.

Chosen Tech Stack

Since the old project suffered from variable and global variable pollution, improper route passing, and excessive usage of mixins, I decided to use TypeScript and Pinia for type and state management.

The old project (Webpack) took about 20 seconds for cold starts or builds, with hot updates at 1-2 seconds, which is acceptable. As the website continuously used Alibaba Cloud's online deployment, I didn't specifically check the bundle size, but it's likely in the tens of MBs. Although these issues are not significant, optimizing static files and removing unused code is always beneficial. Nonetheless, I chose to use Vite for its speed.

To address the variable pollution issue caused by mixins, Vuex, and Vue Router, I selected Vue 3, as it naturally discourages the use of mixins. The Composition API makes it easy to achieve all functionalities previously done using mixins in a more organized manner.

For the new project, I simplified the routing and opted for a single route since the Alarm Platform was removed, and the core functionalities of the other two pages were merged into the Farm Digital Cloud. Therefore, the new version of the Farm Digital Cloud only requires one route to accommodate all functionalities. Additionally, I used the pinia-plugin-persistedstate plugin to persist data globally with Pinia. I must clarify that many people are concerned about storing too much data locally, especially with full persistence like I did. However, when possible, storing some frequently used data or state information locally using localStorage is the most efficient and performant way.

The final tech stack includes: Vue 3 + Pinia + Vue Router + TypeScript.

Let's Get Started

Overview

As the project was already existing, most of the APIs were in place. We quickly sorted out the new requirements, and the backend team worked alongside me, resulting in a rapid development process. By the end of the first week, we had completed 99% of the entire project.

Since there were no new design specifications for the refactored system, I had to rely on the rough style of the old project as a reference (as the logic and architecture were completely different, most parts had to be re-done). Finally, we created a cleaner and more refreshing front-end layout:

New Farm Digital Cloud Page
New Farm Digital Cloud Page

New Alarm Platform Page
New Alarm Platform Page

The login page was also transformed into a lightweight popup for almost all functionalities.

Here's how the complete project looks like:

Complete Project

Comparing it to the old version:

Old Version

Refactor and Optimization

Page Changes

Clearly visible changes include:

  1. The addition of administrative area-level plots.
  2. No more categorization of sowing, unsown, and sample plots; instead, tags are used as badges.
  3. The removal of individual service item displays for each plot; now, the same area is shown when a plot is selected.
  4. The elimination of redundant charts, legends, map controls, and farming records.
  5. Disaster information is now part of the service items and not listed separately.
  6. The timeline is introduced as a replacement for the previous carousel.

Code Changes

Less noticeable changes include:

Removal of all production environment logs

The console is now clean without any logs in the production environment. All logs have been removed, and with TypeScript, runtime errors are minimal. We used autofit.js for easy adaptation to any resolution of the design layout (invincible).

Removal of all third-party major UI component libraries

We did not use any major UI component libraries (we don't care if you tree-shake or not). This resulted in an extremely compact project, with the bundle size being only 1MB+.

Clear code structure

The code structure is clear. Though the content is relatively small right now, it's still evident that the structure is

quite organized. We mostly followed the default structure created by Vite, and there are hardly any static files, which contributes to reducing the bundle size.

Global state persistence using Pinia

Since we used Pinia for global state management throughout the project, we no longer had to worry about when to get or set data. Using stores, we can access and modify data at any time, significantly improving our development experience.

API Categorization and Encapsulation

APIs were categorized and encapsulated, making it clear which vendor's API we are calling. TypeScript ensured that we don't miss or pass incorrect parameters.

Simplified interceptors

We used minimal interceptors, as they are essentially error handlers that ensure the subsequent process does not crash. Therefore, we only implemented the simplest interceptors, which are very useful.

Simplified routing and component usage

As it turned out, we didn't need to use routing transitions. All internal functionalities were presented as lightweight popups. The only redirect was to the payment page, which was part of another project. Our solution was to directly pass the user token to the payment page, straightforward and efficient.

MQTT-inspired Publish-Subscribe Paradigm

We chose to fully rely on Vue 3's watch function. Though it may seem challenging to read at first, it is actually modeled after the publish-subscribe mechanism of MQTT, where watch acts as a subscribe function and store.xxx as a topic. This approach makes the code easy to understand, and it's satisfying to use. If you need to subscribe to a data change and perform some actions, just use watch.

Clearing redundant code

There's no redundant code, none! Function and variable naming are clear and consistent. The primary focus is on clarity. Look at this piece of code; does it need any comments?

Coding conventions

Consistent conventions, I cannot stress enough how important they are. This is a debounce function, it's well-written because I followed the convention!

Type definition using TypeScript

Explicit types - defining types may take an extra five minutes, but it saves an hour during coding.

Development of lightweight essential components

Custom components like selectors and toasts are developed to be lightweight. Only what's necessary is included; it's all about being lightweight.

Online Address

As the project is a live commercial project, I can provide the URL for you to check out and use:

https://farm.sino-eco.com/website/sino_cloud/

All Done

Even though we made additional changes and new requirements after completing 99% in the first week, they were easily managed due to standardized development practices. Coding became enjoyable, and for such a lightweight project, we used a lightweight approach. There's no need to cut a sesame and lose a watermelon, being top-heavy and bottom-heavy, abandoning the core for peripherals, or going in the wrong direction. A frontend project only needs to ensure smooth rendering, no errors, and no crashes. Avoid overcomplicating things; just keep it simple and clean.

About this refactoring experience, it was not only a technical learning process but also a conceptual advancement. Often, a frontend project only needs to ensure smooth rendering, no errors, and no crashes. Avoid overcomplicating things; just keep it simple and clean.

I am Larry, let's keep striving together!

Top comments (0)