DEV Community

Wojciech Maj
Wojciech Maj

Posted on • Edited on

Migrating your React app from Webpack to Vite

What is Vite?

Vite is a "next generation front-end tooling", as its maintainers claim. Instant dev server, fast Hot Module Reload, easy configuration, optimized production builds - it's all there.

But… Vite's documentation doesn't say how to migrate your existing Webpack app 🤷 Worry not! In this guide, we'll get through this together!

Change your repo to ES modules

In your package.json, add the following entry:



  "type": "module",


Enter fullscreen mode Exit fullscreen mode

Embrace modernity! Reject tradition! That's why we're switching to Vite anyway!

Install Vite and its plugins



npm i --save-dev vite @vitejs/plugin-react vite-plugin-simple-html


Enter fullscreen mode Exit fullscreen mode

or



yarn add vite @vitejs/plugin-react vite-plugin-simple-html --dev


Enter fullscreen mode Exit fullscreen mode

Replace scripts

In your package.json, you'll probably have scripts similar to these:



  "build": "NODE_ENV=production webpack",
  "dev": "NODE_ENV=development webpack serve",


Enter fullscreen mode Exit fullscreen mode

build command, invoked by npm run build or yarn build, builds your app for production. dev command starts a development server.

These scripts needs to be replaced with:



  "build": "vite build",
  "dev": "vite serve",


Enter fullscreen mode Exit fullscreen mode

On top of that, you can add one extra command:



  "preview": "vite preview"


Enter fullscreen mode Exit fullscreen mode

preview command will start a server running your app built for production.

Let's run the development server!



  vite v2.9.1 dev server running at:

  > Local: http://localhost:3000/
  > Network: use --host to expose

  ready in 261ms.


Enter fullscreen mode Exit fullscreen mode

👁👄👁 Wow, that is fast.

Teach Vite where the root is

If you started the development server now, Vite will look for index.html in your project's root directory. If it's anywhere else, Vite will not be able to find it and will display an empty page instead.

To fix this, you need to either move index.html to your root directory, or specify a different root directory for Vite to look for index.html in. In my case, it's located at src/index.html.

You can do this by adding root directory path to your commands, like this:



  "build": "vite build src",
  "dev": "vite serve src",


Enter fullscreen mode Exit fullscreen mode

However, you can also do this by creating a vite.config.js file in your project root. You will need it in a short while anyway, so why not create one now to keep all the configuration in one place?



import { defineConfig } from 'vite';

export default defineConfig({
  root: 'src',
  build: {
    // Relative to the root
    outDir: '../dist',
  },
});


Enter fullscreen mode Exit fullscreen mode

Configure vite-plugin-simple-html

Now that Vite knows where to find your index.html file, it will try and parse it.

You may encounter an error like me:

How to deal with it? I was using HtmlWebpackPlugin's templateParameters option to dynamically inject custom title and other info into index.html file, like so:



  <title><%= title %></title>


Enter fullscreen mode Exit fullscreen mode


new HtmlWebpackPlugin({
  template: 'index.html',
  templateParameters: {
    title: env === 'production' ? 'My site' : `My site [${env.toUpperCase()}]`,
  },
}),


Enter fullscreen mode Exit fullscreen mode

Thankfully, we can do the same with vite-plugin-simple-html. In your vite.config.js, add the following bits:



import simpleHtmlPlugin from 'vite-plugin-simple-html';

export default defineConfig({
  // …
  plugins: [
    simpleHtmlPlugin({
      inject: {
        data: {
          title: env === 'production' ? 'My site' : `My site [${env.toUpperCase()}]`,
        },
      },
    }),
  ],
});


Enter fullscreen mode Exit fullscreen mode

Add entry module to your index.html file

At this point, your index.html file should be served just fine. But the app still won't load!

If you used Webpack, you probably have also used html-webpack-plugin to handle injecting <script> tag(s) with your entry module(s) to index.html.

Vite will not inject these tags automatically. You will need to add them by yourself. For example:



<script type="module" src="./index.jsx"></script>


Enter fullscreen mode Exit fullscreen mode

Aaah, that's better. Something came to life.

Image description

Configure @vitejs/plugin-react

Okay, as you can see, we're not quite there yet. We need to configure @vitejs/plugin-react to make it work with React.

If you still used classic JSX runtime, your app may already load at this point, but you'll want to follow these steps anyway. This plugin will not only handle automatic JSX runtime (the one thanks to which you don't need to manually import React in every file), but also add features like Fast Refresh, enable Babel integration, and much, much more.

Add it to your vite.config.js file like so:



import react from '@vitejs/plugin-react';

export default defineConfig({
  // …
  plugins: [
    // …
    react({
      // Use React plugin in all *.jsx and *.tsx files
      include: '**/*.{jsx,tsx}',
    }),
  ],
});


Enter fullscreen mode Exit fullscreen mode

Aliases

At this point, if you were using aliases (to write e.g. import Button from 'src/components/Button' instead of import Button from '../../../../../components/Button'), your build may fail. Easy fix!

Webpack:



  resolve: {
    alias: {
      src: path.resolve(__dirname, 'src'),
    },
  },


Enter fullscreen mode Exit fullscreen mode

Vite:



  resolve: {
    alias: {
      src: path.resolve(__dirname, 'src'),
    }
  },


Enter fullscreen mode Exit fullscreen mode

Wait a second, same thing? 🤔 Yes! Well, kinda. You have more options to choose from, but most of the time, your aliases should work when copied over.

Static files handling

By default, files from public directory placed in your root directory are going to be copied over at build time. If you have these files elsewhere, you can use publicDir option like so:



export default defineConfig({
  // …
  publicDir: '../public',
});


Enter fullscreen mode Exit fullscreen mode

SVG handling

If you used to import SVG icons to use not as static files but rather as React components, @cassidoo just wrote an interesting article on that matter - be sure to check it out!

Babel plugins

You might not need Babel at all, as @babel/preset-env and @babel/preset-react are of no use.

But sometimes Babel plugins may still come in handy. For example, there's a plugin to remove PropTypes you can use to make bundle size a bit smaller, and there's a dedicated plugin for styled-components that makes development and testing easier by, among others, adding component display names.

@vitejs/plugin-react will come to the rescue here, with babel option. For example, to add babel-plugin-styled-components plugin:



    react({
      // …
      babel: {
        plugins: ['babel-plugin-styled-components'],
      },
    },


Enter fullscreen mode Exit fullscreen mode

The process.env.* problem

I was using process.env.NODE_ENV in a bunch of places in my app. This resulted in the following error being thrown in the console:



Uncaught ReferenceError: process is not defined


Enter fullscreen mode Exit fullscreen mode

In Vite, you can use import.meta.env.* instead. For example, process.env.NODE_ENV can be replaced with import.meta.env.NODE_ENV.

Enjoy!

Now you should see your app, powered by Vite!

We're not done yet; we'll still need to tweak a few things before running it in production. For this, you'll have to wait for the second part of this guide. Subscribe to get notified!

Cleaning up

You can safely remove these dependencies, which are now unused:

  • core-js (unless you've been using it directly)
  • webpack (duh)
  • webpack-cli
  • webpack-dev-server
  • *-loader (e.g. babel-loader, style-loader)
  • *-webpack-plugin (e.g. html-webpack-plugin, mini-css-extract-plugin
  • @babel/preset-env
  • @babel/preset-react
  • @pmmmwh/react-refresh-webpack-plugin
  • react-refresh

webpack.config.js Webpack config file can also be deleted.

babel.config.js, babel.config.json, or .babelrc can be deleted, provided that you didn't use it as your Babel config in @vitejs/plugin-react configuration.

Anything missing?

Do you think there's anything else that needs to be addressed, that may be a common problem when migrating from Webpack to Vite? Please, please let me know in the comments!

Top comments (18)

Collapse
 
ekeijl profile image
Edwin • Edited

Great article! Could you add some info about production builds, difference in timing? When using Webpack, the production build often takes longer due to minimization. Would be interesting to see some statistics for Vite.

Another helpful Webpack feature is aliases, that prevent long absolute paths, e.g.

// instead of
import x from '../../../components/x';
// you write
import x from '@components/x'; 
Enter fullscreen mode Exit fullscreen mode

I see Vite has a plugin for it.

Collapse
 
wojtekmaj profile image
Wojciech Maj • Edited

Thanks for the kind words!

Regarding performance - Vite uses esbuild under the hood, and esbuild caused quite a commotion in the industry when it was released.

Enough has been written on this matter so I'll just quote.

Vite pre-bundles dependencies using esbuild. Esbuild is written in Go and pre-bundles dependencies 10-100x faster than JavaScript-based bundlers.

vitejs.dev/guide/why.html
esbuild.github.io/

Collapse
 
jamesthomson profile image
James Thomson

While Vite uses ESBuild during development, it actually uses Rollup for bundling production builds, so the comparisons should be between Webpack & Rollup as ESBuild doesn't weigh in here.

Collapse
 
wintercounter profile image
Victor Vincent

Most of the time building for prod isn't spent on transpilation. That's why OP is asking. I'm using SWC with Webpack which is even faster than ESBuild, yet prod builds take much longer due to optimizations like minification, tree-shaking, bundle chunks, etc...

Collapse
 
yaireo profile image
Yair Even Or

You can also set up aliases in Babel (and not Webpack) using the module-resolver plugin

Collapse
 
wojtekmaj profile image
Wojciech Maj • Edited

An extra tip:

When using Yarn >=2, during installation, you'll get a ton of warnings about esbuild:

The … architecture is incompatible with the this module, link skipped.

You can suppress them by adding the following to yarnrc.yml:

logFilters:
  - code: YN0076
    level: discard
Enter fullscreen mode Exit fullscreen mode
Collapse
 
jamesthomson profile image
James Thomson

Here's another tip if you need to access your Vite env vars within the config:

import { defineConfig, loadEnv } from 'vite';

defineConfig(({ mode }) => {
  // Loads our env file and merges it with Node's process.env
  Object.assign(process.env, loadEnv(mode, process.cwd()));
})
Enter fullscreen mode Exit fullscreen mode

I found this useful when setting up local https for the devserver.

server: {
      port: 8080,
      https: process.env.VITE_HTTPS_CERT_PATH
        ? {
            cert: fs.readFileSync(process.env.VITE_HTTPS_CERT_PATH),
            key: fs.readFileSync(process.env.VITE_HTTPS_KEY_PATH),
          }
        : null,
    }
Enter fullscreen mode Exit fullscreen mode
Collapse
 
elfsternberg profile image
Eλf Sternberg

This is great, even if I'm already using Vite for all my personal projects anyway. One thing I've discovered: if your project was written with Create-React-App, don't even bother ejecting. Just delete everything that wasn't your hand-written code, retaining the src and assets and images and all that, adjust the package.json file as you specified, and iterate until it builds. It takes remarkably little time, and Vite is such a greater pleasure to work with compared to CRA.

Collapse
 
frangaliana profile image
Fran Galiana

I've seen that there could still be incompatibilities with Jest.
Without using the @vite-jest plugin (Authors are working on it yet), how could I configure vite to use jest with testing-library?

Thank you very much for the work and information👌

Collapse
 
cezarneaga profile image
Cezar Neaga

Is there a way to use vite with multiple entries? Meaning having different bundles load in more than one index.html file?

Cheers,
Cezar

Collapse
 
getsetgopi profile image
GP

Does Vite has Webpack Module Federation feature?

Collapse
 
flyfishzy profile image
Joe Zhang

There is a vite plugin for Module Federation github.com/originjs/vite-plugin-fe...

Collapse
 
tarang_workspace profile image
Tarang P

I need help with a project that has been around for approximately 7 years and currently uses Webpack and Babel with specific configurations. I'm seeking support to replace Vite in this particular project.

Thank you.

Collapse
 
eissorcercode99 profile image
The EisSorcer

Why would one want to do this from a practical standpoint? Or rather, what are the use cases that requires this?

Collapse
 
elfsternberg profile image
Eλf Sternberg

Speed and control. Webpack, especially as part of create-react-app, is enormous, and it comes with dozens of side cases and plug-ins that you just do not need. Vite's build system is significantly faster and its dev server lighter weight, plus the plug-ins and features are limited to exactly what you specify, and nothing else.

Collapse
 
getsetgopi profile image
GP

You can create your own webpack config and not rely on RCA. We have created our own react starter kit, it's pretty small and has only what is need for the project. Creating webpack config is super easy and works great.

Thread Thread
 
elfsternberg profile image
Eλf Sternberg

That's true, and I don't disagree. My experience, professionally, has been that most web developers out of bootcamp start with create-react-app as they've been taught, and when the project grows to the point where it no longer serves them well, have to struggle with eject, cra-rewire, or manual migration. Webpack's config is easy to learn, but so is Vite's, and I find Vite's dev-UX just a bit more pleasant.

Collapse
 
jucian0 profile image
Jucian0

Great article! Thanks for sharing