Why bother?
Life is too short to waste it on tedious tasks such as installing dependencies or waiting for the Webpack to finish bundling your whole app even though you’ve made a small label change.
That’s when modern frontend technologies come in. I’ll be focusing on the migration of an typical old setup (~2019) with npm, Webpack and Create React App to something much more modern (and quicker!) utilizing pnpm and Vite.
Let’s quickly introduce our main heros of this article.
Vite is a no-bundle ESM dev server that works a bit differently – it doesn’t bundle anything unlike e.g. Webpack. Because of this Vite‘s sever starts almost instantly and offers pretty much instant page hot reloading.
Pnpm is an alternative to npm and yarn. Usually our project use simillar packages so why install them separately each time? Pnpm takes advantage of that and keeps only one copy of a package. It allows us to install dependencies much faster.
Obviously there’s much more depth in both of those technologies, but that’s not our focus here. So now it’s time for some refactoring, let’s get to it!
It was never meant to be simple
Making a big change to the project like switching out tooling will always break something but our goal is to make the changes bare minimum.
Because we’ll no longer be using Create React App we need to eject by running eject script. It will stop hiding what it’s got installed under the hood and instead eject those things into your project’s package.json for everyone to see.
But first let’s do what’s obvious, we need to remove node_modules and package-lock.json, because pnpm creates it’s own lock file and we cannot reuse previous dependencies.
Then our next best bet is to add two plugins with pnpm (or in package.json) by running below command.
pnpm i vite @vitejs/plugin-react
We also need to update scripts in our package.json with following commands:
- start – starts the dev sever
- preview – starts server and serves files from our build directory
- build – it bundles and minifies our project
"start": "vite", "preview": "vite preview", "build": "tsc && vite build"
Now we need to do small changes to our code structure – we need to keep index.html at the root of our project, not in the public directory.
Last thing that we need to take care of is vite.config.ts which is home to all settings regarding Vite. Bare minimum for a React application is to add reactRefresh plugin, but of course we can configure a plethora of elements here such as proxies, dev server settings and path resolving (which will be important later).
export default defineConfig({ plugins: [ reactRefresh(), ], });
So it’s not that difficult, right? Only pnpm start
and we can get back to development, right? Not really let’s list everything that is wrong with our project now:
- jsx in .js files
- folders from Create React App eject
- svgs doesn’t work
- every import path is broken
So basically our server won’t start unless we fix those issues.
But let’s tackle the simplest issues for now. First of all let’s remove scripts and config folders, we won’t need them anyway. Next, we can’t have jsx in .js or .ts file end of story. Vite won’t allow it so we have no choice but to change them. Depending on your case it may be a few files or every single file in your codebase.
Because we aren’t using Webpack eco system anymore we need to use different loader for svgs. I’ll be using vite-plugin-svgr. It’s npm page provides all necessary info on how to configure it for your project, so I’ll skip that part.
However biggest problem that we have are our import paths. Webpack was fine with paths like import Component from 'components/Component'
, but Vite is not. Unfortunately path resolver in Vite will not accept non-prefixed paths so that means we need to update those paths. First we need to add a resolver to vite.config.ts.
resolve: { alias: { '@': path.resolve(__dirname, 'src'), }, },
And then our path should change to import Component from '@/components/Component'
. I’ve spend quite a lot of time on this problem, but we won’t get away with that change.
There also might be different other things broken depending on what your project actually uses so it’s not a full guide to every issue.
But what about pnpm you might ask and that’s correct, we need to change one thing in our CI config (in our case Gitlab) to make package cache work. Basically we need to update 2 entries. We need to add a path .pnpm-store to cache and install pnpm globally on our image because that’s not something that is provided (read more about robust CI/CD pipelines for Node projects).
cache: key: node_modules paths: - .pnpm-store build UI: stage: build image: node:14.16.0-buster before_script: - curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm@6 - pnpm config set store-dir .pnpm-store script: - pnpm install - pnpm lint - pnpm build artifacts: expire_in: 1d paths: - ./build
Was it worth it?
In short – yes, very much so. Our CI pipeline got reduced by about 2 minutes and because of the hot reload feature of Vite development got much smoother as a result. There were a few hiccups along the way but it wasn’t anything that couldn’t be handled with few Google searches 🙂