Migrate A WebApp From CRA To Vite

Migrate A WebApp From CRA To Vite

September 20th, 2023 | 8 mins read

Published on dev.to and medium.com

Harder Easier, Better, Faster, Stronger 🎵

Introduction

Create React App (or CRA for short) is a must-have in the JavaScript ecosystem, used to "scaffold" / initialize a new React project. Most component-oriented frameworks have their own ("official") CLI tool for this kind of task: @angular/cli a.k.a ng, @vue/cli a.k.a vue, etc...

However, in recent years (2020), a new tool has emerged, popularized by the open-source community (and Evan You, its creator): Vite!

Like CRA which relies mainly on Webpack to provide a ready-to-use JS/TS project; Vite relies on ESBuild (and Rollup's intuitive interface) to create a new Web project. In 2023, Vite stands out as a major player in the current and future Web, by providing the following functionalities:

ESM Native Support

The power of ESM (ECMA Script Module) during development

CRA relies on CommonJS syntax, while adding Babel plugins to support modern functionalities, such as import/export, etc... On the other hand, Vite natively (and implicitly) supports .esm syntax.

In other words, you can develop directly with async / await (at the top level), or import your .tsx / .jsx files directly into the application's entry point (index.html) without worrying about the compilation phase.

ESMs (or explicitly .mjs files) have their own context that allows them to evaluate the latest syntax supported by the JS environment (browsers / NodeJS). * Vite will then compile all this for you!

* NB: By setting the type key to module in the package.json, you convert your project to ESM format. The impact of this concerns your "classic" .js files, that's to say in CommonJS format. Typically configuration files, such as .eslintrc.js or prettier.config.js (which contains this statement: module.exports = {}), will need to be suffixed .cjs to work in an ESM environment. Indeed, a "module" project doesn't implicitly support the CommonJS syntax, as well as for the default projects (non-ESM), which will have to use the .esm mention to natively support some advanced features.

Faster by default

  1. Time saving during development 🚀

The switch from Webpack to Vite comes with a change at the development server level.

With Webpack, providing a development environment requires browsing all the resources (JS files, JSX components, and other modules...) to then launch the development server.

With native support for ECMAScript modules (made possible by ESBuild), Vite runs the development server first, then loads each resource as needed ("code-splitting" style).

Bundle Based Dev-Server

How the Webpack development server works

ESM Native Based Dev-Server

How the Vite development server works

  1. Time saving during compilation 🚀

Another notable change with Vite (and React) is support for Speedy Web Compiler (SWC for short).

When switching from Webpack to ESBuild, you'll have the option of keeping your "good old" Babel compiler to package/build your application, or switching to its Rust equivalent (20x faster in theory).

Currently, not all Vite-based libraries benefit from this feature... But as far as React is concerned, there's a working plugin, so you might as well take advantage of it! 😉

Maintenance

In 2023, Vite is more than mature with these 4 major releases! Unlike Create React App, whose updates are increasingly slow, the open-source community wants to upgrade its new Frontend development tool! Also, Vite currently offers a choice of 7 component-oriented frameworks (with an out-of-the-box TypeScript environment), in addition to VanillaJS:

npm create vite@latest
✔ Project name: … hello-world
? Select a framework: › - Use arrow-keys. Return to submit.
    Vanilla
    Vue
❯   React
    Preact
    Lit
    Svelte
    Solid
    Qwik
    Others

Some of these main frameworks * have already chosen to migrate their CLI tool to Vite (especially vue, but also svelte, which has "dropped" degit in favor of this last one). Maybe it's time to get started!

* NB: Since version 16 of Angular (May 2023), the framework developed by Google offers an ESBuild "preset" to optimize development performance.

Migration (Step-by-Step)

Initialization

The 1st step of this migration work consists in initializing a new React environment based on Vite (rather than installing the dependencies needed for the project one by one...). To do that, I recommend that you follow instructions given by the following command: npm create vite@latest

You should end up with a similar structure, relatively close to Create React App:

/
├── public/
├── src/
│   ├── assets/
│   ├── App.css
│   ├── App.tsx
│   ├── index.css
│   ├── main.tsx
│   └── vite-env.d.ts
├── .eslintrc.cjs # Oh ! CommonJS Syntax :)
├── index.html
├── package.json
├── tsconfig.json
└── tsconfig.node.json
└── vite.config.ts

If you look closer, especially in the package.json, there's no longer any reference to react-script (essential for CRA projects), which was responsible for executing Webpack's specific tasks (start, build, test). This time, everything is based on Vite. *

* NB: In the same way, the npm run preview script available with Vite, is equivalent to the npx serve -s build recommended by Create React App (and Vercel) for testing the release of your Web application.

Configuration

NB: At this point, I invite you to set up a file formatting strategy (via Prettier 😎👌) and configure ESLint according to your needs.

As you can see, the 2nd step consists in configuring your new project. To do that, most of the work is focus on the vite.config.js file.

If you went through the following CLI statement: npm create vite@latest during initialization, you should already have an up-to-date configuration!

However, let’s add support for SVGs (from import statement) and your potential module reference aliases: npm i -D vite-plugin-svgr

import path from 'path';
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc';
import svgr from 'vite-plugin-svgr';

export default defineConfig({
  plugins: [react(), svgr()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
      '~': path.resolve(__dirname, './src')
    }
  }
});

vite.config.js

The baseUrl property associated with paths is essential for TypeScript compilation, as well as the use of the "hyperlink" feature from the IDE. You can also use the configuration below in a jsconfig.json file for JavaScript (non-TS) projects.

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "~/*": ["src/*"]
    }
  }
}

tsconfig.json

Another file to modify (perhaps the most important!? 🤔), the entry point of your application: the index.html

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <!-- Before -->
    <!-- <link rel="icon" type="image/png" href="%PUBLIC_URL%/favicon.webp" /> -->
    <!-- After -->
    <link
      rel="icon"
      type="image/svg+xml"
      href="/vite.svg" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0" />
    <title>Vite + React + TS</title>
  </head>
  <body>
    <div id="root"></div>
    <!-- Oh ! ESM Syntax :D -->
    <script
      type="module"
      src="/src/main.tsx"></script>
  </body>
</html>

✂️ Copy / 📋 Paste

Ctrl +C / +V

The next step is simply to copy / paste the sources present in your previous project / to your new Vite structure. If you have not left the CRA context, simply move the content of the src folder to that of Vite, while paying attention to references in the JavaScript entry point of your project: main.tsx.

NB: Think about aliases! Make sure that all of your imports match your vite.config.js / tsconfig.json or jsconfig.json configuration.

Also consider changing access to environment variables from process.env to import.meta.env (like ESM syntax).

Tests !!! ⚙️

Vitest is the new Jest

Let's not forget the tests during this migration work!

CRA recommends using Jest for unit testing (which is already a good choice). If you have a pre-configured project with Jest, this step should go rather "fast" 🙂

Let's start by installing the new dependencies related to Vitest (replacing Jest): npm i -D vitest @vitest/coverage-v8 @testing-library/jest-dom @testing-library/react jsdom

Then let's configure your project to support Vitest as a global variable:

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc';
import svgr from 'vite-plugin-svgr';

export default defineConfig({
  // ...
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: ['src/setupTests.ts'],
    coverage: {
      provider: 'v8',
      include: ['src/**/*.{js,jsx,ts,tsx}']
    }
  }
});

vite.config.js

{
  "compilerOptions": {
    "types": ["vitest/globals"]
  }
}

tsconfig.json

Finally, replace all references to Jest with Vitest, as follows:

import { vi } from 'vitest';
import { fireEvent, render, screen } from '@testing-library/react';
import MyComponent from '../MyComponent';

test('should triggers click event', () => {
  const onClickMock = vi.fn(); // jest.fn();
  render(<MyComponent onClick={onClickMock} />);
  fireEvent.click(screen.getByText('Click Me'));
  expect(onClickMock).toHaveBeenCalled();
});

Vitest's API is very close to Jest, since it's entirely inspired by this last one. With a few exceptions: importActual. It's up to you to run some tests: npx vitest; or with code coverage: npx vitest --run --coverage

NB: It's possible to emulate Vitest in a smoother interface with the @vitest/ui dependency. I invite you to test that, by simply executing the following instruction: npx vitest -- --ui

At the end of this last step, you will have fully migrated your Web application from CRA to Vite (or at least 99% of your project). Well done! 👏 And... Welcome to the future 🎉🎊

Conclusion

The migration from Create React App to Vite doesn't change the content of the application... However, it significantly improves the Development eXperience (DX), especially with the "Hot Module Replacement" (HMR) support, allowing you to change project resources, without having to restart the development server.

On the other hand, the React community can take advantage of the power of Rust, thanks to a ready-to-use plugin, to gain performance when compiling the project. I guess other component-oriented frameworks will follow that way...

Finally, the Test Runner change (from Jest to Vitest) also boosts performance when running unit tests, due to a lighter (and modern) library.

Despite its young age (compared to others), Vite is increasingly used (as well as ESBuild) with more than 6 million NPM downloads in August 2023 (i.e. NPM Trends), unlike Webpack, which sees its number of users decline...

NB: Note that, Vite benefits from a multitude of community plugins, including support for Electron, Storybook and PWA mode.

Choosing Vite means having a head start in Web development, because of its simplicity, its compatibility with Rollup, but also due to the ability to change frameworks (at any time) without major breaking changes: "one bundler to rule them all!"