Using react (& friends) with vite - New generation javascript tooling goes brrrr...⚡

callmeberzerker Sun Jul 25 2021

In this post I will try to summarize all the choices I made when building a starter (aka. template) for React using the new generation fast-tooling like vite (powered by esbuild).

Oh no a big wall of text -> you go and look at the github repo.

… But first some history 📚

In times long gone, most of us in the React world were happy using create-react-app (hence abbreviated to CRA) as our template project. Heck even apple (yes that apple) was using CRA to deploy a promotional site by using CRA scaffolding. And we all accepted the limitations of the scaffolding that was provided by us, even though they were reasonable but still in some niche cases limiting.

The core tech-stack behind CRA are webpack as a bundler, babel as a transpiler and jest for testing. There is also TypeScript (hence abbreviated TS) typechecking being done by tsc (the typescript compiler) - but without using TS to bundle the application.

So what changed?

If you’ve ever used webpack & babel you might not need introduction; they are kinda slow. I will not delve into what makes them “slow” but they definitely need several seconds to bundle/transpile your code.

Frustration with the slowness of our javascript based tooling arose. A tool like esbuild (made by Evan Wallace) showcased that tooling which is compiled to native (in Go programming language) can speed the transpilation/bundling process 10-100 times.🤯

Bundling benchmark - taken from https://github.com/evanw/esbuild
Bundling benchmark - taken from https://github.com/evanw/esbuild

Then thanks to Evan You (creator of vue framework) who figured out that serving the project files over native ES modules and using esbuild (not babel) to transpile the code (JSX, TypeScript, etc…) we can achieve almost instantaneous dev server startup and have super smooth HMR out of the box. Win.

The core tech-stack behind vite are esbuild for “transpilation” during development, rollup for production bundling (why not esbuild is because esbuild is still somewhat new and not with extensive plugin ecosystem) and it’s own unique way of handling and serving files during development. That’s why vite has many plugins prefixed vite-plugin- that tries to bridge that disconnect between rollup and esbuild.

All of this is great and you can get a nice react project setup going just by creating a vite app, where you can choose react -> and then react-ts as your template. It even comes out of the box with react’s own fast-refresh that basically means on every file change (while the dev server is running) your new code will be replaced via HMR with your React component state being preserved. 🔥

Setting up the template

So I started out from vite react-ts template and went on to try to configure all the technologies I like to use…

Enter pain…

styled-components

So first of all since I like to use styled-components in my React projects I wanted to configure styled-components-macro which uses babel (duh) but it was straight-forward to setup in vite due to it being abstracted in a vite-plugin-babel-macros.

Just as I was thinking I dodged a bullet having to configure babel, I went on configuring jest to run on my project…

Jest

First a foreword vite probably will have first class jest support but it’s not there yet (and I am kinda skeptical now how much of it can be safely abstracted away). You can watch this issue when it eventually lands. At the time of this writing there was still some work that needed to be done on jest side to support vite’s async transformers.

Some of the solutions offered in the comments there weren’t good for me since it kinda broke my VS Code Jest Runner extension.

If you ever configured jest manually you know that thing is complex as all hell. It has bazillion of configuration knobs, has partial support for ES modules. (ES Modules were made stable in Node-land just recently)

Since vite is doing its transforms w/o babel you kinda have to use babel-jest as your transformer in jest.config.js.

… which means - welcome back babel I missed (configuring) you! 😅.

Welcome back meme
Oh babel, ye fickle mistress..

The last time I had to manually tweak babel plugins, presets was 4-5 years ago (thanks CRA!) so once more unto the breach. After I fixed it for the tests (which was arduous process with many trials and errors, even though it’s couple lines of code) it broke dev(???) and vice versa. If you are asking how did babel, which was supposedly not running in dev (or build), broke the dev build - it’s because since vite-plugin-babel-macros requires babel under the hood and if it detects .babelrc file in your project (which needs to be there for babel-jest) it also runs all the presets and turns it into cjs which is not supported by vite. So bye bye declarative configuration formats welcome back babel.config.js -> I used process.env['NODE_ENV'] to conditionally include presets/plugins based on it and declared victory. (to be honest I could also tried to configure babel-jest inline in jest.config.js but I was somewhat wading through errors at this point left and right… so this was Good™)

Other tech choices

I will just list a general libraries that most using React are using -> prettier, react-router-dom, TS.

GraphQL

I use custom script download-graphql-schema.js to download the schema.graphql from my GraphQL api and then generate TS types out of it.

husky & lint-staged

I use husky to run pre-commit hook that in combination with lint-staged will run those files through prettier if applicable.

react-final-form

I use react-final-form as a go-to form library -> it’s battle-tested and I highly encourage you to try it out.

eslint

Configured with my hack-and-slash way of configuring eslint -> if a rule I deem necessary I add it -> if something annoys me and I don’t see the benefit I remove it. I highly encourage you to do the same.

Also had to use eslint.js (instead of a static format like .eslintrc) because I want to read the schema.graphql so I can configure graphql eslint plugin to validate my gql tagged string literals for possible violations (ie. fetching non-existent fields).

react-testing-library

If you want to run component tests react-testing-library is your best choice. You can take a look at a test sample here

urql

I really like the API (and the underlying architecture/thought process) of urql graphql client. Built by the guys from Formidable it really is the best and light(er) alternative to Apollo. Highly recommended it.

react-router (v6)

Even though still in beta (v6 that is), I’ve been using it in a real app for the past 3 months and have found no issues. I think a stable release is coming soon - but it’s definitely an improvement over v5.

TypeScript

Nothing special here, just configuration of paths so we don’t have none of that ../../../SomeComponent.tsx nonsense but stable paths like src/shared/components/SomeComponent.tsx. Be sure to also configure your vite/rollup configuration to resolving those paths (see here)

Closing thoughts

To configure some javascript libs to play nice to each other can be pain, each comes with certain expectations that don’t align with other libs expectations. Each library has it’s own configuration file so enjoy the config file explosion at the root of the folder 😅. The whole node ecosystem is also going through a quirky phase with ESM becoming officially supported and the general shift from cjs -> so expect a few libraries to have “bad” package.json entry point definitions for cjs vs esm. The general rule of thumb there is if you find an issue with such a package -> create an issue at their github repository.

This starter has all my preferred tech stack choices -> but you can easily remove/add parts that suit your need. You don’t like GraphQL but like REST -> rip off anything GraphQL and feel free to add react query. You don’t like final form and prefer formik -> swap it out.

If you use this template or find a way to improve the setup (or make things more idiomatic) feel free to reach me via twitter (link below 👇).

Happy hacking everyone! 🍻