Migrating a CRA monorepo to Vite
Filip Polić
8 min read
Hello developers! Look at your monorepo. Now back to ours. Now back at yours. Now back to ours. Sadly, your monorepo is a lot slower than ours, but if you stopped using CRA / Webpack, it could be as fast as ours!
Now, that sounds a lot like the hit Old Spice commercial, doesn’t it? You’re 100% right, it does. Jokes aside, the first paragraph is kind of how we felt when we started using Vite instead of Webpack.
What is Vite?
If you’re like me, that means you don’t usually get too excited when you see a new JS compiler, framework or a library. Probably because there are so many of them that it’s impossible to keep track of the newest one. If you don’t believe me, try your luck at https://npmdrinkinggame.party. If you manage to enter a name that doesn’t exist in the NPM registry, I’ll buy you a beer. 🍻
Vite is a build tool that simplifies the build process and frontend development in general. I was pleasantly surprised when I started learning more about it. It was quite easy to get started with and learn about key concepts, configurations and levels of customisation it provides. It’s well maintained and there are a lot of community plugins available. It has hella stars on GitHub (the most important thing, obviously) and even has support for SSR. Also, the documentation is pretty good. Not perfect, but pretty good.
Do you even monorepo, bro?
This is when things get a bit tricky, though. I’ll try and simplify the project structure we’ve had with CRA, which was pretty easy to set up a year or two ago. So even though it’s not technically a monorepo, we’ve set up an app with multiple entry points.
Suppose we have 3 apps called blog
, portfolio
and webshop
. Each of those apps is a separate folder with its own App.tsx
file. The current folder structure looks something like this:
The next step was to figure out how to individually start one of the apps without importing all of the entry points in index.tsx
. We achieved this by using environment variables which requires small changes to the package.json
file.
By setting the REACT_APP_BUILD_TARGET
environment variable, we are now able to make use of the dynamic import syntax. Since the import function returns a promise we’re going to use the .then()
method to access the result. It would have been great to use the async/await
syntax instead, but unfortunately top-level await is still unsupported.
This approach worked and we’ve been using it for almost 2 years now. This is where Vite comes in. Although it should have been fairly easy to replace CRA with Vite, and for the most part it was, there were a couple of hiccups with dynamic imports that we weren’t able to solve in the same way as we did with CRA. Using the import
nor the import.meta.glob
functions did not work as expected. The 2 most common problems were that Vite couldn’t parse the template strings (when using the import
function) and that the whole codebase was imported regardless of whether it was actually used or not (when using the import.meta.glob
function). That was a bit weird, to say the least, but we kept trying different approaches and unfortunately none of them worked.
Possible solutions
Luckily, we had some ideas that should work without dynamically importing modules. The first one was to create a small Vite plugin that would dynamically generate the index.tsx
file in which the import path for the root App
component would depend on the current VITE_BUILD_TARGET
(former REACT_APP_BUILD_TARGET
-> check out Vite’s env setup for better understanding).
This worked great until we tried to start multiple apps at once. That would cause an issue because each time a new app was started, the index.tsx
file would be updated with the new import path for the App
component, which would always point to the app that was most recently started. That was obviously not what we wanted to achieve so we had to give the second idea a try.
This idea relies mostly on the index.html
file. Let me explain. So, remember how you could interpolate environment variables in index.html
with CRA and their little hidden Webpack magic? Turns out that’s pretty simple to do with Vite as well. There are a couple of community plugins available for this but we decided to build our own since it was really simple:
All that was needed was to find all occurrences between 2 %
signs and replace them with the values from the environment config. This provided us with a “dynamic” index.html
file. Since the index.html
file has to contain a script tag that loads the index.tsx
file, we realised we could use that to our advantage:
The /src/%VITE_BUILD_TARGET%/App.tsx
expression might look a bit weird but it enables us to start multiple apps at once.
For example, when starting the blog
app the src
attribute would be transformed to /src/blog/App.tsx
.
Vite or bust?
All in all, we really like Vite. Mainly because it made the development and CI/CD processes a lot faster and easier to manage. Whether you’re debating to migrate your CRA project(s) to Vite, start a new project with Vite or something else, Vite has probably got you covered. 10/10 would recommend.
I realise this looks like a sponsored blog post but it really isn’t. That’s exactly what someone who’s writing a sponsored blog post would say, right?
Filip Polić
Software Engineer
Explore more
articles
We shape our core capabilities around lean product teams capable of delivering immense value to organisations worldwide
Got a project?
Let's have a chat!
Zagreb Office
Radnička cesta 39
Split Office
Put Orišca 11, 2nd floor
Contact info
Split+Zagreb, Croatia
+385 91 395 9711info@profico.hr