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:
Weather Monitoring Page (NavBar disappeared, oh my 😅)
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:
(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:
Alright, this one is also decent, and along with the template, it's understandable. Let's see another one:
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:
The login page was also transformed into a lightweight popup for almost all functionalities.
Here's how the complete project looks like:
Comparing it to the old version:
Refactor and Optimization
Page Changes
Clearly visible changes include:
- The addition of administrative area-level plots.
- No more categorization of sowing, unsown, and sample plots; instead, tags are used as badges.
- The removal of individual service item displays for each plot; now, the same area is shown when a plot is selected.
- The elimination of redundant charts, legends, map controls, and farming records.
- Disaster information is now part of the service items and not listed separately.
- 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)