Migrating a Create React App (CRA) application to Vite

Photo by Markus Spiske on Unsplash

I had an existing app that was scaffolded using create react app (CRA) and extended with craco. CRA didn't support the tooling I needed so I had to look for an alternative. I found Vite.

There are some incredible improvements in Vite over CRA, including PostCSS 8.0 support so I decided to migrate my production application.

I'll explain some of the benefits of Vite and describe the steps you need to take to upgrade your application.

Update October 2022: I changed @vitejs/plugin-react-refresh to @vitejs/plugin-react. The former is deprecated now.

Why Vite over create react app?

Vite has all of the features in CRA but with better implementations and it has additional features CRA doesn't support.

These are three main benefits of Vite for my application.

Speed

Vite is 10-20 times faster that CRA because it uses esbuild instead of webpack under the hood. It's hard to describe how much faster it feels but all changes are essentially instantaneous in your browser.

One thing to note is that Vite doesn't type check your code. It only transpiles TypeScript to JavaScript. So you might find some static bugs when building later in your development pipeline. Your IDE should help with this though.

Native ECMAScript module support

Vite supports ES modules natively. It allows you to develop for the browser with bare imports like typscript and it converts them in to proper versioned imports on build

// you write your code with bare import
import myModule from "./folder/myModule";

// vite converts to usable import
import myModule from "./folder/myModule.js?v=abc123";

CSS Plugin support

Vite supports modern tooling like PostCSS 8.0 much earlier than create react app. At the time of writing this post CRA had no support for PostCSS 8.0. Vite is under more active development. If you want to use tailwind CSS without awkward webpack shims you should use Vite.

You can see the full list of features here

Browser support for Vite builds in 2021

Vite's default target requires Native ES6 modules. Being able to use native es6 modules reduces your bundle size because it's easier to remove unused code.

Browser support for native ES6 modules is available on most desktop browsers since 2019. Same for mobile although support is more fragmented. You can review the support here on caniuse.com.

If you need to target older browsers you can use the official plugin from @vitejs/plugin-legacy. This will load the required polyfills to support older targets.

Planning to upgrade to Vite

Vite is almost a drop in replacement for CRA if you already use TypeScript. You will need to make some changes to your code but you should be able to just find and replace for most of those.

These are the steps we will follow to migrate.

  1. Update your package.json
  2. Add a Vite config
  3. Update your tsconfig.json file
  4. Install all the packages
  5. Move your index.html file
  6. Update the index.html contents
  7. Update all your env vars

Let's go!

1. Update your package.json

Change your package.json scripts to use Vite. Don’t worry about not having it yet, we will install it later.

  "scripts": {
    "start": "vite",
    "build": "tsc && vite build",
    "serve": "vite preview",
  },

Make sure you delete react-scripts from your dependencies section.

Add some new devDependencies for Vite.

    "@vitejs/plugin-react": "2.2.0",
    "vite-plugin-svgr" :"0.3.0",
    "vite": "3.2.2"

or if you want to install these directly

yarn add vite @vitejs/plugin-react-refresh vite-plugin-svgr

//or
// npm i vite @vitejs/plugin-react-refresh vite-plugin-svgr

and you can add PostCSS 8+ to your dependencies now if you need it!

2.Add a Vite config

Add vite.config.ts to the root of your project. I just use this basic configuration to start with.

import { defineConfig } from "vite";
import reactRefresh from "@vitejs/plugin-react";
import svgrPlugin from "vite-plugin-svgr";

// https://vitejs.dev/config/
export default defineConfig({
  // This changes the out put dir from dist to build
  // comment this out if that isn't relevant for your project
  build: {
    outDir: "build",
  },
  plugins: [
    reactRefresh(),
    svgrPlugin({
      svgrOptions: {
        icon: true,
        // ...svgr options (https://react-svgr.com/docs/options/)
      },
    }),
  ],
});

3. Update your tsconfig.json

You must set the tsconfig.json to use esnext as a target,lib and module type. This might change in future versions of TypeScripts as more esnext functionality is added to the es standard for a given year so check the Vite site for updated config if this article is old.

Add the vite types to the types section. This tells TypeScript about the special Vite browser functionality that it provides for us.

Finally don't forget to set isolatedModules to true if you don't have that already. There is some modern Typescript functionally that is not supported by Vite yet.

{
  "compilerOptions": {
    "target": "ESNext",
    "lib": ["dom", "dom.iterable", "esnext"],
    "types": ["vite/client", "vite-plugin-svgr/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"]
}

4. Install to update everything

Run yarn or npm i to install all the new dependencies we have added to the project.

5. Move your index.html file

Move the index.html from /public out to the root of the project.

Vite doesn't need the index.html to be in the public folder any more.

6. Update the content of index.html

Vite handles urls in the index.html differently to create react app.

Remove any %PUBLIC_URL% references from the file. Just replace that string with "".

{/* This is the create react app url. change this to not have the variable... */}
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />

{/* ... to be like this. This is the correct url for Vite */}
<link rel="icon" href="/favicon.ico" />

Add a script tag with the project entrypoint

<body>
  <noscript>You need to enable JavaScript to run this app.</noscript>
  <div id="root"></div>
  {/* Like below. This is the script tag for bootstrapping your Vite application */}
  <script type="module" src="/src/index.tsx"></script>
</body>

7. Update all your env vars if you use them

Rename your environment variables so they start with VITE_ e.g. search and replace REACT_APP_toVITE_

# this create react app variable
REACT_APP_MY_VAR
# should be this in Vite
VITE_MY_VAR

Vite uses the ECMAScript module import.meta properties to pass environment variables to the modules.

To access these environment variables you must change all process.env.s to import.meta.env..

You should be able to search and replace this.

Additional notes for large existing projects

Vite recommends using css modules for your application. I use tailwind CSS in my app but if you use sass or css you might need to install the sass preprocessor.

yarn add -D sass

//or
// npm i --save-dev sass

If you must have react or vue environment variables available in process.env for your Vite application then you can use the plugin vite-plugin-env-compatible.

The plugin will load VUE_APP_ or REACT_APP_ environment variables to process.env. You might need this if you are using a library that expects an env var to be on process.env for example.

yarn add vite-plugin-env-compatible

//or
// npm i vite-plugin-env-compatible

and add

 envCompatible(/* options */)

to your vite.config.ts plugins section.

:heart: Thanks to Asher Cohen for these tips. There's even more detail on his comment here.

Done!

That’s it. Now try running your app with yarn start or npm run start

Let me know if anything didn’t work for you!