In recent years, web development has seen a significant shift towards faster and more efficient tooling. Create-React-App (CRA) has been a popular choice for bootstrapping React applications, but it has some limitations, especially when it comes to the development server's speed and optimized production builds. This is where Vite, a modern front-end build tool, comes into play.
Why migrate from CRA to Vite?
Vite addresses some of the pain points of CRA by offering a faster development experience and improved production build optimization.
- Fast Cold Server Start: Vite utilizes native ES modules, which enables faster module loading during development. This results in a much quicker cold server start time compared to CRA.
- Optimized Builds: Vite utilizes Rollup for production builds, which provides better tree shaking and code optimization compared to CRA's Webpack configuration.
- Out-of-the-box CSS Code Splitting: Vite offers CSS code splitting without any additional configuration, which can lead to smaller bundle sizes and faster page loads.
- Better TypeScript Support: Vite has built-in support for TypeScript, which eliminates the need for additional plugins and configurations compared to CRA.
- Flexible Configuration: Vite offers a more flexible configuration system, allowing developers to easily customize their build setup without ejecting the project.
Project Details
Before we begin, here's some information about our project:
- It is a React application created using CRA and using Webpack version 5.
- We used Antd Library and Formik for form elements.
- We used styled components to define CSS.
Migration Steps
To migrate from CRA to Vite, these are the steps we followed:
1. Install Vite:
Install Vite by running
npm install -D vite
2. Update package.json
:
Change your package.json scripts to use Vite. Make sure to delete react-scripts
from your dependencies section.
"scripts": {
"start": "vite",
"build": "tsc && vite build",
"serve": "vite preview",
},
3. Create a Vite Configuration File: Create a vite.config.js
file at the root of your project with the provided content. We have also added the configuration for styled components within the Babel plugins section.
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import viteTsconfigPaths from 'vite-tsconfig-paths';
export default defineConfig({
// depending on your application, base can also be "/"
base: '',
plugins: [
react({
include: /.(jsx|tsx)$/,
babel: {
plugins: ['styled-components'],
babelrc: false,
configFile: false,
},
}),
viteTsconfigPaths(),
],
define: {
'process.env': {},
// By default, Vite doesn't include shims for NodeJS/
// necessary for segment analytics lib to work
global: {},
},
resolve: {
alias: [
{
find: 'antd/lib',
replacement: 'antd/es',
},
{
find: 'crypto',
replacement: 'crypto-js',
},
],
},
server: {
// this ensures that the browser opens upon server start
open: true,
// this sets a default port to 3000
port: 3000,
},
});
4. Update your tsconfig.json
Make sure to configure your tsconfig.json
file with the following settings:
- Set the target, lib, and module type to
esnext
. This configuration might need to be updated in future TypeScript versions as more esnext functionality becomes part of the ECMAScript standard. Always refer to the Vite documentation for potential updates if this article is outdated. - Add the Vite types to the
types
section. This inclusion informs TypeScript about the unique Vite browser functionalities that are available for your project. - Enable the
isolatedModules
option. This setting is crucial because some modern TypeScript features are not yet supported by Vite. By settingisolatedModules
totrue
, you ensure better compatibility with Vite. If this option is not already enabled in your configuration, make sure to add it.
{
"compilerOptions": {
"target": "ESNext",
"lib": ["dom", "dom.iterable", "esnext"],
"types": ["vite/client"],
"allowJs": false,
"skipLibCheck": false,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"]
}
5. Move the index.html
File
Vite has a root directory which your files are served from. Since index.html
is the entry point for Vite's server, the file needs to be in the root directory.
From the public directory, move the index.html
file to the root of your project.
6. Update the context of index.html
Vite handles URLs in the index.html differently to create react app.
index.html
with CRA:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
Remove any %PUBLIC_URL%
references from the file. Just replace that string with ""
. Add a script tag with the project entry point index.html
with Vite:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
<!-- Like below. This is the script tag for bootstrapping your Vite application -->
<script type="module" src="/src/index.tsx"></script>
</body>
</html>
7. Update Environment Variables
Vite uses a different naming convention and access method for environment variables compared to Create React App. To update your environment variables, follow these steps:
- Rename your environment variables to start with
VITE_
. For example, replaceREACT_APP_MY_VAR
withVITE_MY_VAR
.
.env file with CRA:
REACT_APP_BE_URL=https://react.dev/
REACT_APP_SECRET_KEY = "CRATOVITEMIGRATION"
.env file with Vite:
VITE_BE_URL=https://react.dev/
VITE_SECRET_KEY = "CRATOVITEMIGRATION"
- Vite uses the ECMAScript module
import.meta
properties to pass environment variables to the modules. To access these environment variables, change allprocess.env.
references toimport.meta.env.
.
You can easily search and replace these occurrences in your codebase.
Here's an example of how to access an environment variable in Vite:
const myVar = import.meta.env.VITE_MY_VAR;
8. Add missing missing Node polyfills:
You might face issues with your application not rendering correctly, accompanied by warnings like Module "buffer" has been externalized for browser compatibility
. This issue arises due to missing Node polyfills in your bundled web application, as the polyfills provided by our SDK are not compatible with Vite.
To resolve this, you can specify your own compatible polyfills in your Vite configuration.
- Install the package vite-plugin-node-polyfills
This package offers a collection of polyfills for Node packages that are compatible with Vite.
To install using npm, run:
npm i --save-dev vite-plugin-node-polyfills
Alternatively, to install using yarn, run:
yarn add --dev vite-plugin-node-polyfills
9. Configuring Vite to use the plugin
Navigate to your Vite configuration file, typically located in the root of your project and named either vite.config.js
or vite.config.ts
. From here, add the line import { nodePolyfills } from 'vite-plugin-node-polyfills'
to the top, and add nodePolyfills()
to the plugins array inside defineConfig
as in the example below.
For a complete list of available polyfills, refer to the official documentation.
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import viteTsconfigPaths from 'vite-tsconfig-paths';
import { nodePolyfills } from 'vite-plugin-node-polyfills';
export default defineConfig({
// depending on your application, base can also be "/"
base: '',
plugins: [
react({
include: /.(jsx|tsx)$/,
babel: {
plugins: ['styled-components'],
babelrc: false,
configFile: false,
},
}),
viteTsconfigPaths(),
nodePolyfills({
include: ['buffer', 'fs', 'stream', 'util'],
}),
],
define: {
'process.env': {},
// By default, Vite doesn't include shims for NodeJS/
// necessary for segment analytics lib to work
global: {},
},
resolve: {
alias: [
{
find: 'antd/lib',
replacement: 'antd/es',
},
{
find: 'crypto',
replacement: 'crypto-js',
},
],
},
server: {
// this ensures that the browser opens upon server start
open: true,
// this sets a default port to 3000
port: 3000,
},
});
Note:If you are transitioning from CRA to Vite and relied on CRA for automatic routing, you will need to manually remap the routing when using Vite.
Conclusion
In summary, migrating from Create-React-App (CRA) to Vite offers numerous benefits, including faster development server start times, optimized production builds, and better TypeScript support. To migrate, you can follow the outlined steps, such as installing Vite, updating package.json scripts, creating a Vite configuration file, updating tsconfig.json, moving the index.html file, updating its context, updating environment variables, and adding missing Node polyfills. By doing so, we reduced the build time by 50%.