Migrate A WebApp From CRA To Vite
Tags: javascript, react, vite, vitest
Published: 2023-09-20 06:16:56

> ~~Harder~~ Easier, Better, Faster, Stronger 🎵

## Introduction

**C**reate **R**eact **A**pp (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 <u>Evan You</u>, its creator): **Vite**!

Like CRA which relies mainly on [Webpack](https://webpack.js.org) to provide a ready-to-use JS/TS project; Vite relies on [ESBuild](https://esbuild.github.io) (and [Rollup](https://rollupjs.org)'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** (**E**CMA **S**cript **M**odule) 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uzl7ggwq1gs3ss5ynnz2.png)

<figcaption>How the Webpack development server works</figcaption>

![ESM Native Based Dev-Server](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9pgqnbg21wbixo4qm7bx.png)

<figcaption>How the Vite development server works</figcaption>

2. Time saving during compilation 🚀

Another notable change with Vite (and React) is support for **S**peedy **W**eb **C**ompiler ([SWC](https://swc.rs) for short).

When switching from Webpack to ESBuild, you'll have the option of keeping your "_good old_" [Babel](https://babeljs.io) 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! Moreover, 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`

```js
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')
    }
  }
});
```

<figcaption>vite.config.js</figcaption>

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.

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

<figcaption>tsconfig.json</figcaption>

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

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <!-- Before -->
    <!-- <link rel="icon" type="image/png" href="%PUBLIC_URL%/favicon.png" /> -->
    <!-- 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](https://jestjs.io) 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](https://vitest.dev) (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:

```js
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}']
    }
  }
});
```

<figcaption>vite.config.js</figcaption>

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

<figcaption>tsconfig.json</figcaption>

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

```jsx
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](https://vitest.dev/api/vi.html#vi-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 "**H**ot **M**odule **R**eplacement" (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](https://npmtrends.com/esbuild-vs-rollup-vs-vite-vs-webpack)), unlike Webpack, which sees its number of users decline...

> **NB**: Note that, Vite benefits from a multitude of [community plugins](https://vitejs.dev/plugins/#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!_"