Migration to Vite: the journey towards faster builds

Introduction 

Considering that an application’s build time is crucial for efficient deployment, it is of utmost importance for it to be optimized. In this article, I will present a success story of how we greatly decreased the build time of our accelerator, Simple CMS, from 1 hour and 50 minutes to merely 10 seconds. 

 

A bit about the Simple CMS structure 

Since its inception, Simple CMS has been using React.js as its framework, combined with TypeScript and various external libraries.The project was initially created with two main packages in its root folder, app and library. This structure was chosen so that the library package could be decoupled from Simple CMS, allowing it to be used in other projects, although this feature was never utilized. 

After undergoing a major refactor in mid-2023, the project adopted a monorepo format—a repository containing multiple applications — using the Turborepo bundler. The app and library packages were then moved inside the packages directory, as shown below, and have remained there since. 


Monorepo structure Simple CMS 

 

Packages directory 

The packages directory contained a configuration that, using Turborepo, allowed both packages (app and library) to run simultaneously for either compiling or building the project. It had its own package.json file with these scripts and a node_modules directory for the installed external libraries. 

 

Library package 

The library package served as the core of the application, housing all components, contexts, pages, and essential project code. Its build configuration was set up with Webpack, utilizing the ts-loader compiler with the transpileOnly option enabled. This configuration allowed the project to run without requiring all TypeScript types to be fully correct, potentially speeding up the build process. Finally, the package was exported as a local Node library to the node_modules directory located in the project’s root folder. 

 

App package 

The app package had a singular role: to import the library package from the node_modules directory in the project’s root folder and execute the application. Please note that the application within the app package was built using the Create React App library and relied on react-scripts for its execution scripts. 

 

The Build problem 

Now that you have an overview of the application’s structure, let’s delve deeper into the specific issue with our build process. When we executed the npm run build command in the monorepo root folder, Turborepo ran this command in both the app and library packages simultaneously. This process resulted in the generation of an artifact ready for deployment. However, the problem arose from the excessive time required to complete this process, despite its successful execution, as illustrated below. 


Build time in monorepo format 

 

Compiling problems 

In addition to our poorly optimized build process, we encountered several issues related to code compiling and re-compiling between changes. 

 

Issue #1
The application took over a minute to start and load completely. 

 

Issue #2
Changes made in the library package took over a minute to reflect on-screen. 

 

Issue #3
After making more than two changes to our code, regardless of size, the application would halt with an error displayed in the terminal (as shown below). 

This error occurred when Node wasn’t able to allocate sufficient memory for running the application, requiring a restart to resume development. This delay forced us to endure another lengthy wait for the project to recompile. 

To address these issues, we conducted several tests to try and pinpoint the root causes of these prolonged delays in both the build and project compilation processes. Below, I will provide a detailed account of our testing procedures and findings. 

 

Library package tests

Since the library package contained all the structures used by the application and handled all project tasks, and considering we never made changes to the app package, my initial conclusion was that the issue had to be within the library package itself. 

Given that the package was configured using Webpack, I began investigating potential causes of the build delay directly within the bundler. I also reached out to our Innovation Studio for insights into the issue. They provided valuable tips, including a recommendation to use the Speed Measure Plugin (for Webpack). 

This plugin logs detailed and visually friendly information in the console about the time taken for each process during the build. After executing the npm run build command at the root folder, the plugin revealed that our library package completed its build in just 11 seconds, as shown in the image below, while the app package continued to build. 


Speed Measure Plugin test 

 

Upon reviewing the results, it became evident that the build delay was not originating from the library package itself, its build time was 11 seconds. 

 

App package tests 

Following the conclusion from our previous test, I turned my attention to the app package, where the root of the issue quickly became apparent. 

As mentioned earlier, the application was built using Create React App (CRA), which utilizes the react-scripts library for script execution. While CRA was once a staple among React developers, its prominence has faded over time with the emergence of alternatives that showed superior performance metrics, such as reduced initialization, load times, and build durations. Moreover, CRA has been deprecated since 2023, meaning it no longer receives updates or optimizations. 

In the images below, it’s important to note that the app package does not include a specific bundler configuration file like webpack.config.js. Instead, it relies solely on the standard react-scripts defined in its package.json scripts. 

That said, running the npm run build script inside this specific package will cause a 50-minute-long delay in completing the process, compared to the library package which, in comparison, only takes 20 seconds. 

That said, upon executing the npm run build command within the app package, significant delays were encountered, with the process taking approximately 50 minutes to complete— a stark contrast to the library package, which finished in just 11 seconds, as depicted in the images below. 


app package 

 


app package scripts package.json 

 

Seeking alternatives 

Based on the insights and information gathered, I started looking for a comprehensive solution to address all three issues previously mentioned. The initial approach involved adding a webpack.config.js file to the app package and migrating its standard CRA configurations to Webpack. The results of this adjustment on the overall project build time are illustrated below. 


Project build with app package in Webpack

 

As shown, there was a noticeable reduction in the build time from its original state. However, this alone did not resolve all our application’s issues. Even after migrating the app package to Webpack, the project still showed a prolonged initialization and slow reload times for any changes made. This issue originated from an aspect mentioned earlier in the Simple CMS structure section. 

Each time a change is made to the library package, it undergoes a complete reconstruction and is compiled into the node_modules folder at the project’s root, where the app package imports it. This process is inherently slow and causes delays as the library package acts as a Node library that updates in real time during our application’s development. 

In order for us to understand this process a little bit better, here is an overview of the structure of the library package, followed by an illustration of how the app package imports the library: 


library package where code is developed 

 


Exported library package inside node_modules 

 

As seen in the images above, our library package is entirely exported to node_modules, thereby serving as the core source for the application. 

 

Solution 

Considering the primary goal of a monorepo structure is to house multiple applications within the same repository, and recognizing that the library package was intended to be decoupled for use in other projects, simply adjusting configurations or import methods within the current setup seemed inadequate. 

The required change to address our issues couldn’t be limited to configuration adjustments alone, as I had initially attempted. It required a major structural change to the Simple CMS project. This meant transitioning our application out of the monorepo format by consolidating the app and library directories and their contents into a single project, thereby streamlining our development environment. 

To find a suitable solution, I researched bundlers that, nowadays, are known for better performance when compared to Webpack. I quickly concluded that Vite was the ideal bundler for migrating Simple CMS. What convinced me was Vite’s documentation, which explicitly addresses key pain points such as Slow Server Start and Slow Updates—issues that had been significant concerns for our project. 

Moreover, Vite provides a straightforward configuration and exceptionally accessible documentation, making it one of the top choices for modern React projects. 

 

Below is the new structure of Simple CMS: 


New Simple CMS using Vite 

 

Conclusion 

With Simple CMS fully migrated to Vite and removed from the monorepo structure, the project has achieved significant speed improvements and considerably enhanced performance. This enhancement not only resolved the build time issues for our deployments, as planned but also hugely boosted the productivity of our Front-End team. They now have the capacity to deliver tasks with even more agility. 

Previously, creating a Front-End build for Simple CMS required nearly 2 hours, whereas now it takes only 10 seconds. Code recompilation happens almost instantly, eliminating the need to wait nearly a minute even for minor changes like a console log. 

However, it’s important to acknowledge that these improvements came with a trade-off. Removing the project from its monorepo structure means our library package can no longer beeasily decoupled for use in other projects. Despite this, upon closer examination of the benefits gained, the decision was justified and ultimately benefited the project as a whole. 

During my research, I acquired insights into the complexities of software development decision-making. However, what remains clear to me is the importance of creating solutions that meet the specific needs of each project. Thus, concluding that there is no perfect solution in software development. 

 

PLEASE NOTE: Some information regarding the project structure was slightly changed for this article. 

 

*The opinions expressed here reflect my personal views and do not necessarily represent the views of Compass UOL. 

Gostou da solução? Nós podemos ajudar!

Conheça nossos conteúdos gratuitos, direcionados aos assuntos de sua preferência!

Enviar

Receba nosso conteúdo

Gostaria de receber de forma gratuita mais conteúdos sobre este ou outros assuntos? Preencha o formulário abaixo e receba nosso conteúdo gratuito!

Parabéns!

Você receberá nosso conteúdo em breve!

Atenção

Tivemos um problema com seu formulário, tente novamente.