My notes about conducting a massive refactor in a Vue.js website
Refactors are never easy, especially in core parts of a software product, and some developers struggle by just hearing the word "refactor" (myself included).
Realizing the need for changes
In the company I work, called RuaDois, we have a Vue.js front-end, that is used by brazilian real estates to track and boost their businesses.
The application started as an MVP which states for Minimum Viable Project, in 2018. When I joined the team, the application was already built and running, with some features implemented in it.
But after some months of work, I started to feel the website was taking too much time to load, about 15~20 seconds, which is annoying.
I ran a google lighthouse simple test, and the results, well, were terrible:
As the application grew faster and faster, by the hands of different developers, with different levels of experience, the software engineering practices were almost gone. There was a lot of duplicated code, unused libs, unused components, unused assets, old code that wasn't being removed, and so on, that I realized after a couple of months working on the project.
This lack of good practices and concern with the code could be potentially dangerous to the health of the application, and in a long term perspective, could lead the future developers to lose control of the code, making maintenance painful, and preventing the application to scale with a solid foundation, which by no means is a good thing to the clients and the company.
After seeing this, I decided to make something about it, because the application was already huge, and if nothing was done, it could get worse over time, making the user experience not pleasant. The users may not feel the website easy to use anymore, and instead, frustrating and time-consuming.
I started to think of a plan to refactor the whole application, from removing duplicated code to revamping its whole architecture to make the code easier to maintain. Quite a challenge, I'd say.
At the same time, looking to introduce software engineering practices, that were lost at some point in the past.
Getting to the numbers
The Vue CLI has a
--report option, which can generate a detailed HTML report about the build size.
Here is one of the first reports, from the size of the application at that time:
As you can see on the left, the build had the size of almost 30 MB!
- Moment was taking up about 2 MB of space;
- Lodash was taking 1.35 MB of space;
- Vee-validate was taking 860 KB of space;
- v-tooltip was about 600 KB;
- vue-toastr-2 required jquery and toastr dependencies, totalizing 450 KB;
In a first patch, I updated vee-validate's version to 3.0, and its size dropped to 240 KB, pretty good!
Then, I updated the
lodash imports. Across the entire application, there were imports of the whole library as
import _ from 'lodash'. I've changed that to
import sortBy from 'lodash/sortBy' and so on. After that, its size is about 200 KB.
I decided to remove moment entirely from the application and use date-fns instead. Here's a good read about it. Date-fns uses only 140 KB in my build.
Note: by the time I'm publishing this post, moment is now officially deprecated.
I removed the v-tooltip lib because it was being used only in one place of the app.
I replaced vue-toastr-2 for vue-notification.
Hint: Bundlephobia is a good website to check the size of a lib you want to add to your application.
Cleaning up the mess
After cleaning up the biggest imports, I started to dig on the unused components looking to remove them. Same for the unused assets, and the last part: cleaning up duplicated code: by creating mixins, filters, or just by simplifying the code to make it more maintainable and clean.
The router needed special attention: it was holding all the application's routes in a single file, with more than 1000 lines, and the routes were imported statically, which means they were all downloaded at the same time in the client, even if they weren't being used at the user's current navigation. They were creating a big single file with all the routes loaded. By changing the imports to use the dynamic approach, we can get the benefits of lazy loading.
The components had the same treatment, the ones that could be lazily loaded, I've updated the imports to something like:
MyComponent: () => import('@/modules/components/MyComponent')
One thing I've also noticed: the image files were too big, some of them were about 2 MB up to 3 MB in .png, so I compressed them to the .webp format without losing quality. Each image has now a size of about 200 KB. Not bad!
And for the last part: I removed some of the global components because they were also taking up too much space without being used. I decided to keep as global components only the ones that were being used massively across the application. This saved about 500 KB.
After that, the build size dropped to less than 10 MB!
Ensuring good practices
As I was revamping the application's code, I decided to also create solid foundations for code quality, this way I can ensure its quality as the application grows from the hands of different developers over time.
I started by updating the ESlint lib, its rules were a little bit "permissive", so I straighten them a bit more, by changing the rules to the
The style section needed attention, its code never followed any pattern, except for some little BEM. I removed the sass-lint lib, which was unmaintained at this time, and added the stylelint lib, with some custom configs, one for scss and another for its ordering.
- For the scss I've used stylelint-config-sass-guidelines;
- To ensure the css ordering, I decided to go for the concentric pattern (a good read, btw) with stylelint-config-concentric-order.
To make sure these rules would continue to be followed, I updated the review stage on the GitLab CI to check them.
We've also enabled the .gzip compression to help decrease downloading times in the client.
Auditing the new results
After two patches were sent to production, I've run some lighthouse tests again, and this time the results were satisfying:
Ready for new challenges
After spending a lot of time with a lot of changes, I feel I can implement new features without worrying about something's not right, because the foundations weren't solid enough.
Of course over time these foundations might be changed because of requirements update, or maybe someone comes up with a new, better architecture solution and so on...
Software is always evolving and increasing its challenging levels.
Creating a good software product is not about how much you code, is about how critically you look at it. Sounds weird?
What I mean by that is: look at your code from different perspectives, do some research, wonder if there are better solutions, ask questions, strive for quality, performance, maintainability, clean code, user experience, and so on...
Also, important to note: enjoy the process (that moment when you face a big puzzle in front of you) and celebrate the little victories (the good old Hello World's).
Last but not least: happy hacking!
Thanks for reading! :)