Using Vite with React
Published: 2021-05-10 19:31:28

## Background

It all started when I had to configure another simple thing in webpack (4.x). It's a SPA monorepo app written with React, TypeScript, and styled-components. It takes ~2 minutes to start the dev server for the UI. Which is kind of frustrating. The fast-refresh is also not that fast, and you can see a notable delay between updates.

Understanding that migration to managed SSR solutions like Next.js takes more time, effort, and a stiff learning curve for my team, I had an opportunity to integrate one of the new shiny bundle tools that use esbuild + ES modules (ESM) with almost not config. [esbuild](https://github.com/evanw/esbuild) is "An extremely fast JavaScript bundler and minifier". EXTREMELY FAST:

![esbuild is fast](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4e5nedaff56aofu7bso3.png)

[Why it's so fast?](https://github.com/evanw/esbuild) because it's written in go and does a lot of optimizations.
[ES modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) is a new browser feature that enables to **natively** load modules - meaning no need to bundle everything in one giant bundle. We can split the code into smaller modules and load them as needed. This helps avoid unnecessary work and stay fast no matter how big the project grows.

![regular server](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2wmzp2963awd90k07c5e.png)


![esm dev server](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mdnuyln9kx308m1ykr1j.png)

I already played with those tools with some little demos at home for my learning. But now I had to find a way to use them in the production app in my company.

## Snowpack vs Vite vs WMR

It did not take a lot of time to choose the tool. Currently, there are 3 major bundlers powered by ESM [](https://esbuild.github.io/)- [vite](http://vitejs.dev/), [snowpack](https://www.snowpack.dev/), and [wmr](https://github.com/preactjs/wmr). From the first glance - all of them look promising and I did not care about their internals as long as I don't need to touch them.

I chose Vite for 2 main reasons:

- Vite handles monorepos better (snowpack work [is still in progress](https://github.com/snowpackjs/snowpack/discussions/938)).
- wmr does not have [error overlay / SVGR support](https://github.com/preactjs/wmr/issues/128)

I think that soon - those issues will be resolved, but for now, I bet on vite.

For deeper comparison, you can look at [this article](https://vitejs.dev/guide/comparisons.html#snowpack) from vite's documentation, but also notice that some of the latest snowpack progression does not mention there.

One of the benefits I like about those tools is that they require less configuration to write - they all support all the major workarounds out of the box. So you don't tie into complex configs like in webpack. You don't need to config anything to support TypeScript, React, Babel, manage public or static assets, fast-refresh, dynamic imports, etc... This makes migration easy if we will want.

> The code you don't have to write - is the code that doesn't lift you from migration. It's not a major bet if you don't have to risk much.

## The journey to configure vite

Looking at the example at vite-create-app [ts-react template](https://github.com/vitejs/vite/tree/main/packages/create-app/template-react-ts) I was able to configure 80% of the work.

But I already know that it's one of those cases that those last 20% take more time to configure than the first 80%. Those tiny bugs / unmatched stuff you have to fix. Some of them are:

- I had to fix some unrelated TypeScript type definitions. I don't know why do we have code like `const f = <T,>() => return f2<T>()` and why vite (or it may be a rollup / TypeScript issue?) struggles to transpile it - I convert this, for now, to `<T extends unknown>` which is equivalent to make it work.
- We already have configured path aliases with [webpack and tsconfig](https://github.com/dividab/tsconfig-paths-webpack-plugin#readme) - so I wanted to keep that behavior. Luckily we have the [vite-tsconfig-paths](https://github.com/aleclarson/vite-tsconfig-paths#readme) exactly for that. It worked well. I could also try using the [alias](https://vitejs.dev/config/#resolve-alias) for that.
- Instead of [webpack svgr](https://github.com/gregberge/svgr/tree/main/packages/webpack) - vite has a community plugin [vite-plugin-svgr](https://github.com/pd4d10/vite-plugin-svgr#readme). Looking at its [source code](https://github.com/pd4d10/vite-plugin-svgr/blob/main/src/index.ts) I can tell that it does the work as needed. In the future, I may want to add more [SVGR options](https://react-svgr.com/docs/options/) (currently for custom colors we use css [currentColor](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#currentcolor_keyword) property), so I may contribute it or create a similar plugin. NOTE: vite bundles the static svg in addition to assets even if you use vite-plugin-svgr. So many useless files will be generated. This is a minor issue that can be unnoticeable. I created a script for deleting those files (it has 0 impacts on the bundle because the client does not download those chunks).
- Vite docs [recommended](https://vitejs.dev/guide/features.html#client-types) specifying `vite/client` types in `tsconfig.json`. Before we did not explicitly specify this property - so we have to include other needed types like jest and node

    ```json
    {
      "compilerOptions": {
        "types": ["vite/client"]
      }
    }
    ```

## Issues with 3rd party libraries

- Small [issue](https://github.com/tannerlinsley/react-query/issues/1936) with [react-query](https://react-query.tanstack.com/) devtools that solved by changing to a different minor version. But it's more an [issue with the library](https://github.com/vitejs/vite/issues/1676#issuecomment-766302418).
- [react-markdown](https://github.com/remarkjs/react-markdown) v4 also has [an issue](https://github.com/remarkjs/react-markdown/issues/492) using `process.cwd` internally. I upgraded to v5 and the problem is gone. It seems that like in webpack 5 - you won't get and node.js polyfills with vite.

Now for the harder problems:

- Some of our components depend on `process.env.NODE_ENV`. Our old webpack config uses [define-plugin](https://webpack.js.org/plugins/define-plugin/) to replace them with their node.js env values. So I specify them with [define](https://vitejs.dev/config/#define):

    ```tsx
    define: {
      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
    }
    ```

- Some third-party libraries like [nivo](https://github.com/plouc/nivo) for graphs use internally Node.js specific `global` keyword ([here](https://github.com/plouc/nivo/blob/99b520e9621e77472b8c922d85a0fcd7bc5e00cc/packages/network/src/props.js#L87)). But ES modules do not support it - and we get a Reference not found error for `global` variable. What vite tries to do is to convert them to be ESM - and run them in the browser. But the browser does not know what global is - so it fails. At first, I try to handle this in `vite.config.ts` with define - setting `global: {}` . But doing so created a problem with `import GlobalStyles from "./global.style"` then converted to be unwanted `import GlobalStyles from "./{}.style"` . So until nivo will upgrade to ES-modules - I hacked that global variable by pre-load in `index.html` like

    ```tsx
    <script>window.global = {}</script>
    <script src="./src/index.tsx"/>
    ```

## Env variables

The next thing I had to handle is the env variables. vite put the env variables inside `import.meta.env` (did you know `import.meta` is [in the spec](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import.meta) + [node.js](https://nodejs.org/api/esm.html#esm_import_meta) compatible?). Like next.js - if you want to expose them to the client-side you can use the `VITE_APP_YOUR_VARIABLE_NAME` - which is nice. For TypeScript users - vite docs encourage to add to `tsconfig.json`'s `compilerOptions` with `types:["vite/client"]` . But explicit writing types entry - you lose your jest, testing-library and node typings library supports. So you have to manually specify them too. You can now get vite typings - but they are still missing your env variables types. You can declare them like [here](https://stackoverflow.com/questions/66039933/typescript-types-for-import-meta-env).

I wonder it's better to just use dotenv + env variables that like I did with define?

It makes stuff simplified.

## Jest

I had a problem with our tests with the `ts-jest` preset that did not understand what `import.meta` is. So I kept using `process.env` in our code instead of `import.meta`. This may not be a concern for you because it looks like vite is planning to create its own [jest preset](https://github.com/vitejs/vite/issues/1149) to solve this problem.

## Bundle analyze

In our "old" build system - I configured [webpack-bundle-analyze](https://github.com/webpack-contrib/webpack-bundle-analyzer) to inspect bundle chunks and understand what it made of. I added [rollup-bundle-visualyzer](https://github.com/btd/rollup-plugin-visualizer) instead (although there is [an issue](https://github.com/vitejs/vite/issues/898) that the reported size is not correct).

## The final `vite.config.ts`

```tsx
import reactRefresh from "@vitejs/plugin-react-refresh";
import bundleVisualizer from "rollup-plugin-visualizer";
import { defineConfig } from "vite";
import svgr from "vite-plugin-svgr";
import tsconfigPaths from "vite-tsconfig-paths";

function getBundleVisualizerPlugin() {
    return {
        ...bundleVisualizer({
            template: "treemap", // or sunburst
            open: true,
            gzipSize: true,
            brotliSize: true,
        }),
        apply: "build",
        enforce: "post",
    } as Plugin;
};

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [
        reactRefresh(),
        svgr(),
        tsconfigPaths(),
        process.env.BUNDLE_ANALYZE === "1" && getBundleVisualizerPlugin(),
    ].filter(Boolean),
    define: {
        "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
    },
});
```

- I will change the `as Plugin` type assertion when [my PR](https://github.com/DefinitelyTyped/DefinitelyTyped/pull/51923) to `@types/rollup-bundle-visualyzer` will be merged

## Summary

All the struggle to configure vite since the moment I decided until my PR merged took one day. Now we have a fast development environment for improved productivity. I'm sure that not all 3rd party libraries are compatible with vite. But things are getting better. The tech world evolves and I hope that library authors will eventually supply maintain ESM compatible build.

My conclusion is that if you won't need SSR (which [experimentally supported in vite](https://vitejs.dev/guide/ssr.html)), I encourage you to try vite / other ESM build tools. It will make your dev environment really fast. Start with the [docs](http://vitejs.dev/), Skim [https://github.com/vitejs/awesome-vite](https://github.com/vitejs/awesome-vite) and look at the open issues too. It will make your dev environment really fast.