Reducing React Dev Server to 213ms — From Create React App to Vite v3 Migration
In this article, I share the steps to migrating CRA (create react app) to use Vite v3 while highlighting the benefits.
Join the DZone community and get the full member experience.
Join For FreeDeveloper experience is important. Having a fast and reliable development and build solution is important as the code that is written and how it performs. Recently, I saw a significant slow-down in a react app that its development and build system was based on the latest CRA package (create react app/react-script).
I improved the developer experience in both development and build for production from a
few seconds to a mere 213ms from 3277ms (dev server) — That's an improvement of 95% in server uptime. In this article, I share my steps for migrating CRA (create react app) to use Vite while highlighting the benefits.
With Vite (After):
With CRA (Before):
Why Vite.js?
The JS ecosystem is a wild ride — every once in a while, there is a new library/package/some other solution that comes with a promise to improve developer life in the JS client, server, and even both. The last JS evolution that popularized SSR and SSG introduced Next.js, Remix, fresh, and more as a full-stack JavaScript framework for web applications and static websites. However, not everyone is joining that ride — I assume some don't get the full benefits while others might be stuck in a complex migration, and some don't even need the server-side benefits — in which case these are client-side apps (CSA).
In my case, I was looking for a CRA alternative that would not require me to introduce a JS server and any significant opinionated architecture and code change. I was interested in migrating to Next.js or Remix.
However, I dismissed both because they required me to make a huge change to my application while also requiring a learning curve that I could not afford due to time constraints.
Issues With CRA
CRA has become for my projects very sluggish and had a few issues. For starters, the first dev server start took around 45 seconds. While at the same time, cold starts took about eight to nine seconds.
I had to migrate storybook v6.5 to use Vite.js builder — since it didn't work with the latest CRA v5 because it's based on webpack version 5. Due to that, the build process for the storybook failed because webpack still exists in the system (at the time of writing this article, it is still an issue).
The hot module reload/refresh was problematic and not satisfying — with few changes, the entire app was reloaded, and sometimes I had to manually reload.
Lastly, maintenance and the new version release have gone silent for quite some time now.
Migrating to Vite.js
Version 3 was just published, and I wanted to try it after reading and seeing some examples that looked good. The ability to easily configure or have a "zero configuration" appealed to me.
My CRA app uses React 18, Typescript, Chakra-UI, and some SVG files.
Vite configuration lives in a vite.config.ts file at the root of the project (I chose to go with the Typescript version, although JS also works).
The Base Configuration for Compiling React, Typescript, and SVG
To support all the above and match the build of CRA, this configuration worked:
These npm packages are required:
npm i vite vite-plugin-svgr vite-tsconfig-paths @vitejs/plugin-react
import { defineConfig } from 'vite'
import svgrPlugin from 'vite-plugin-svgr'
import tsconfigPaths from 'vite-tsconfig-paths'
import react from '@vitejs/plugin-react'
import path from 'path';
export default defineConfig({
server: {
port: 3210,
},
build: {
outDir: 'build',
},
plugins: [
react(),
tsconfigPaths(),
svgrPlugin({
svgrOptions: {
icon: true,
// ...svgr options (https://react-svgr.com/docs/options/)
},
}),
],
})
I believe the code is pretty much self-explanatory. One of the most dominant components is the plugin entry — which allows composing pluggable features to vite's build system.
Some of these plugin functions usually get a configuration object as a parameter, which lets you further customize what and how a plugin should do with the compiled code.
To complement typings and following some documentation on Vite, tsconfig.ts was updated with these:
{
"target": "ESNext",
"esModuleInterop": false,
"jsx": "react-jsx",
"types": [
"vite/client",
"vite-plugin-svgr/client",
],
"paths": {
"~/*": ["./*"]
}
}
On top of this basic configuration, I added the following plugins to support more features:
npm i @vitejs/plugin-basic-ssl vite-plugin-mkcert vite-plugin-pwa
// adding workbox to support pwa features
npm i workbox-core workbox-precaching workbox-routing workbox-window
export default defineConfig({
plugins: [
enableSsl && basicSsl(),
mkcert(),
// ...
// ... plugins from above
// ...
VitePWA({
manifest, // imported from ./manifest.json
includeAssets: ['favicon.ico', 'robots.txt', 'images/*.png'],
devOptions: {
enabled: false,
},
workbox: {
globPatterns: ['**/*.{js,css,html}', '**/*.{svg,png,jpg,gif}'],
maximumFileSizeToCacheInBytes: 3000000,
},
}),
],
})
My readm app is a PWA app, which at this point, installs a cached version to speed up loading time on the next time a user opens the app. The app is also installable as a desktop app on any platform, desktop, and mobile.
There's more to PWA configuration — i.e., showing an update is available - for that, I had to use a dedicated solution, which is out of the scope of this article. I followed examples from the excellent VitePWA documentation and examples. I add vite-plugin-pwa/client to the tsconfig/types section.
PWA apps require an SSL connection and so — basicSsl() and mkcert() simply set up a local HTTPS server with a locally signed certificate — This step required a few steps when I was using CRA.
Updating Index.html and Environment Variables
The main entry point to the app, index.html, has to be in the root. I removed any %PUBLIC_URL% instances as these were not needed. Vite requires a plugin to embed environment variables values from .env — I chose to remove those completely from the index and embed the codes statically (Google Analytics).
A few image references in the HTML were updated with a full path starting from the root, i.e. src/assets/images/readm.png.
Vite requires indicating the starting file for the app with a simple script tag before the closing body tag:
<script type="module" src="/src/index.tsx"></script>
As for environment variables defined in .env, I had to replace any REACT_APP_ with VITE_. To use these variables across the app, you would use them like that:
import.met.env.VITE_THE_VARIABLE
src/react-app-env.d.ts has been renamed src/env.d.ts
Configure the Test With Vitest
create-react-app was using jest and react-testing-library. With Vite, I had to install the vitest plugin and add its configuration to vite.config.ts. To run a test with a virtual dom, I had to install two additional packages as well:
npm i vitest c8 jsdom
// added this typing reference
/// <reference types="vitest" />
export default defineConfig({
test: {
include: ['src/**/__tests__/*'],
globals: true,
environment: 'jsdom',
setupFiles: 'src/setupTests.ts',
clearMocks: true,
coverage: {
enabled: true,
'100': true,
reporter: ['text', 'lcov'],
reportsDirectory: 'coverage/jest'
}
},
// ... rest of the config
}
The difference in vitest vs. jest was replacing stub functions:
// BEFORE
const speak = jest.fn()
// AFTER
import { vi } from 'vitest'
const speak = vi.fn()
Updating Package.json Scripts
Finally, to make it all work, we must update the scripts that are building the production app and starting the server.
This is easily done with the following:
{
"start": "vite",
"build": "vite build",
"test": "vitest",
"test:ci": "vitest run"
}
With everything in place, I removed react scripts as well.
Conclusion
Moving from CRA to Vite improved my developer experience by far. As the screenshot at the beginning of the article shows, the dev server is up in ~213ms (the first run takes 1-2 seconds) compared to CRA - which took around 3277ms or more (the first run adds 4-5 seconds) — that's a reduction of about 95% in for the dev server startup.
The additional benefit in the developer experience is the hot module reload during development — changes are injected live into the working server — updating specific files for the update and reloading the entire app. That's a huge improvement from the previous CRA development.
The build process has also been improved — around 10 seconds with a live indicator and hints on how to improve it. That same build took around 40 seconds in CRA
with no live indicator.
Feels like readm is now steroids — I recommend anyone who's still using create-react-app to make this effort and consider migrating to Vite.
Published at DZone with permission of Oren Farhi. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments