Moving from CRA to Vite

Vite offers improved build times, more efficient code splitting and a better developer experience. This is how Asserts replaced CRA with Vite.

Moving from CRA to Vite

React is a popular JavaScript library for building user interfaces. Create React App (CRA) is one of the most popular tools for bootstrapping React applications. CRA provides a simple way to start a new React project, including all the configuration and boilerplate code needed to get up and running quickly. However, as modern web development evolves, new tools and frameworks emerge that offer different features and benefits.

One such tool is Vite, a build tool and development server designed for modern web development. Vite offers many advantages over CRA, including faster build times, improved development experience, and more efficient code splitting.

If you're considering migrating your existing React project from CRA to Vite, this guide will walk you through the process.

Reasons to migrate

Here are some reasons why you might consider migrating your React project from CRA to Vite.

Faster build times

One of the biggest advantages of Vite over CRA is its faster build times. Vite leverages modern browser capabilities to perform faster builds, making the development process more efficient. In addition, Vite can perform "incremental" builds by only recompiling files that have changed since the last build, resulting in significantly faster build times.

Improved development experience

Vite provides a more streamlined and customizable development environment that allows for faster iteration and debugging. It's worth mentioning that Vite doesn't block developers from viewing the result of modified code. Therefore, they don't need to resolve all TypeScript errors before previewing the result in the browser. This is considered a good developer experience and can speed up development.

Better performance

Vite uses a "module-based" approach to code splitting, which means it only loads the modules that are needed for a given page or component, resulting in faster load times and better overall performance. This is in contrast to CRA's approach, which uses a "route-based" approach to code splitting, which can lead to larger and slower-loading bundles.

Easier to configure

While CRA provides a straightforward way to get up and running quickly, it can be more difficult to customize and configure for specific use cases. Vite, on the other hand, provides a more flexible and configurable environment that allows for greater customization and optimization of your development workflow.

Since React is going in the server direction there are Next.js, Remix, and Gatsby recommendations on the official Start a New React Project page but no mentions of CRA at all.

Migration steps

These migration steps assume that we have a CRA project with Typescript.

Step 1: Install dependencies

At the time of migration, the following npm libraries needed to be installed. Please note that not all of these libraries may be required for your particular project, and the specific dependencies may vary depending on your project's requirements.

  1. vite (Vite itself. All the dependencies below will be Vite's plugins).
  2. @vitejs/plugin-react (the default Vite plugin for React projects).
  3. vite-plugin-svgr (Vite plugin to transform SVGs into React components. Uses svgr under the hood).
  4. vite-tsconfig-paths (Gives Vite the ability to resolve imports using TypeScript's path mapping).
  5. vite-plugin-checker (A Vite plugin that can run TypeScript, VLS, vue-tsc, ESLint, and Stylelint in worker thread.).
  6. vite-plugin-handlebars (Vite support for Handlebars. We use this plugin to move all the snippet parts away from index.html).
npm install --save-dev vite @vitejs/plugin-react vite-tsconfig-paths vite-plugin-svgr

Step 2: Create a vite.config.ts

Once Vite is installed, you'll need to create a vite.config.ts file at the root of your project. This file is used to configure Vite and tell it how to build your project. Here's a basic example considering the packages that were required for us:

import { defineConfig, Plugin } from 'vite';
import { resolve } from 'path';
import react from '@vitejs/plugin-react';
import viteTsconfigPaths from 'vite-tsconfig-paths';
import svgrPlugin from 'vite-plugin-svgr';
import checker from 'vite-plugin-checker';
import handlebars from 'vite-plugin-handlebars';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    react(),
    checker({
      overlay: { initialIsOpen: false },
      typescript: true,
      eslint: {
        lintCommand: 'eslint "./src/**/*.{ts,tsx}"',
      },
    }),
    viteTsconfigPaths(),
    svgrPlugin(),
    handlebars({
      partialDirectory: resolve(__dirname, 'src/partials'),
    }) as Plugin,
  ],
  server: {
    port: 3000,
    proxy: {
      '/api-server/': '...',
      '/authorization/': '...',
    },
  },
});
Example of vite.config.ts

Step 3: Moving and updating your index.html

In CRA index.html is located in the /public folder. According to the Vite docs, it should be moved to the root.

CRA's %PUBLIC_URL% no longer needed and should be removed from index.html.

Also, we need to add an entry point. Vite provides a simple and elegant HTML way to do this:

<script type="module" src="/src/index.tsx"></script>

Step 4: Update Typescript configs

Update tsconfig.json fields: target, lib, types .

{
  "compilerOptions": {
    "target": "ESNext",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": false,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "noFallthroughCasesInSwitch": true,
    "jsx": "react-jsx",
    "types": ["vite/client", "vite-plugin-svgr/client"],
  },
  "include": [
    "src"
  ]
}
Example of tsconfig.json

And we need to add vite-env.d.ts file in the src folder with the contents:

/// <reference types="vite/client" />
vite-env.d.ts

Step 5: Changing CRA-specific settings

If you use process.env.REACT_APP_ variables you will have to change all of them to import.meta.env. For example:

// before
if (process.env.NODE_ENV === 'production') {

}

// after
if (import.meta.env.PROD) {

}

Also, Vite has a bit different naming. For example, the output-dir name for Vite is dist instead of build so make sure you either adapt everything to this change or use vite.config.ts to sort it out:

export default defineConfig({
  ...
  build: {
    outDir: 'build',
  },
});
vite.config.ts

Step 6: Uninstall CRA (react-scripts)

Now we can say thanks to CRA and get rid of it using the following command:

npm uninstall react-scripts

Also, don't forget to update package.json scripts:

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

Now we are all set and can start the project!

Troubleshooting Vite HMR issue

Although we were happy with our migration to a new platform, our satisfaction was not complete as we soon discovered that HMR (Hot Module Replacement) was not functioning properly. We noticed that a full reload happens instead of HMR every time files were changed. The possible causes are described in Vite's docs here and in our case was a dependency loop.

What is circular dependency?

Circular dependency in JavaScript occurs when two or more modules depend on each other in a way that creates a loop. This means that Module A depends on Module B, which in turn depends on Module C, and then Module C depends back on Module A. This creates a cycle or loop, which can cause issues with the module system.

The problem with circular dependencies is that they can lead to a situation where modules are not properly resolved or loaded, which can result in unexpected behavior or errors. For example, if a module is not properly loaded due to a circular dependency, it may cause a reference error or undefined variable, which can be difficult to track down and resolve.

To avoid circular dependencies, it is important to design your code and module architecture in a way that ensures that dependencies are properly defined and managed. This can involve breaking up modules into smaller, more focused pieces, avoiding cross-dependencies between modules, and using tools or techniques such as dependency injection or lazy loading to manage dependencies more effectively.

Finding circular dependencies

For finding and fixing circular dependencies we used Madge. Madge is a developer tool for generating a visual graph of your module dependencies, finding circular dependencies, and giving you other useful info. We used it with the following command:

npx madge --circular ./

After resolving all of the circular dependencies in our codebase, we were finally able to get HMR working properly. The result was a lightning-fast development experience that far surpassed what we had previously experienced with CRA.

Conclusion

Migrating from Create React App (CRA) to Vite can provide a significant boost to development speed and efficiency. Vite's lightning-fast build times and powerful HMR (Hot Module Replacement) functionality make it a top choice for modern web development, particularly for those working with React.

While the migration process may require some initial setup and configuration, the benefits are well worth the effort. With Vite, developers can enjoy a more streamlined development process, with shorter build times and instant updates to their code. Additionally, Vite's advanced features such as pre-bundling and tree shaking can help optimize performance and improve the user experience.

Overall, the decision to migrate from CRA to Vite will depend on the specific needs and goals of your development team. However, for those looking to streamline their workflow and optimize their development process, Vite is a powerful tool that can help take their projects to the next level.