Initial situation
The initial situation is a React frontend that is still based on react-scripts sets. In order to modernize this and bring all dependencies up to date (especially TypeScript), I have changed the tooling to Vite migrated.
Why Vite?
Vite is advertised as a modern tooling variant that is quick and easy and is intended to improve the developer experience. In particular, Vite offers the following advantages out-of-the-box:
- Faster dev server start times through native ES modules
- Hot Module Replacement (HMR)
- Simplified configuration
Install Vite and uninstall react-scripts
In this Deep Dive I use npm as a package manager, so I use the corresponding npm commands.
First, I install Vite and the required React plugin:
npm install --save-dev vite @vitejs/plugin-react
Then I remove react-scripts from the dependencies:
npm uninstall react-scripts
Update package.json
In my package.json I can now simply use the react-scripts-commands with the corresponding Vite commands, for example:
Before:
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build"
}
After:
"scripts": {
"start": "vite",
"build": "vite build"
}
Customize environment variables
There was already a first difficulty here in my project. For local development, I use environment variables that were passed to the process via my start script (via env-cmd from a .env-file):
"start": "docker-compose -f localdeployment/docker-compose.yml up -d && env-cmd -f .env.local react-scripts start"
Vite replaces environment variables statically, and I can no longer use process.env to access them. Instead, I had to adjust the variables in the individual files of the respective stage and change them as follows:
REACT_APP_BASE_IMAGE_URL=/
becomes
VITE_BASE_IMAGE_URL=/
With the prefix VITE_ Vite recognizes the respective variable. After the change, I now have access to it via import.meta.env.VITE_BASE_IMAGE_URL and can adjust the corresponding position in the code.
The previously used start script changes to :
"start": "docker-compose -f localdeployment/docker-compose.yml up -d && vite"
Create the Vite configuration
// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import svgr from 'vite-plugin-svgr';
import eslintPlugin from 'vite-plugin-eslint';
export default defineConfig(({ mode }) => {
let base = '';
if (mode !== 'development') {
base = '/test/basepath';
}
return {
plugins: [react(), eslintPlugin(), svgr()],
base,
build: {
outDir: 'build',
sourcemap: false,
assetsDir: '',
cssCodeSplit: true,
rollupOptions: { output: {
entryFileNames: 'static/js/main.[hash].js',
chunkFileNames: (assetInfo) => {
if (assetInfo.name === 'index') {
return 'static/js/main.[hash].js';
}
return 'static/js/[name].[hash].chunk.js';
},
assetFileNames: (assetInfo) => {
if (assetInfo.name && assetInfo.name.endsWith('.css')) {
return 'static/css/main.[hash].css';
} else if (
assetInfo.name &&
/\.(png|jpe?g|gif|svg|webp|ico|avif)$/.test(assetInfo.name)
) {
return 'static/media/[name].[hash][extname]';
} else if (
assetInfo.name &&
/\.(woff2?|eot|ttf|otf)$/.test(assetInfo.name)
) {
return 'static/media/[name].[hash][extname]';
} else {
return 'static/media/[name].[hash][extname]';
}
},
},
},
},
server: {
port: 3000,
},
};
});
To get the same behavior as before, I had to configure a few things.
The most important point is the React pluginso that Vite knows that I have a React app. Also, Vite doesn't have built-in ESLint support; I had to add it via a plugin:
npm install --save-dev vite-plugin-eslint
I also use many SVGs as React components in my application, which is not natively supported by Vite. For this I use the svgr-plugin:
npm install --save-dev vite-plugin-svgr
One time-consuming point was to adapt the build so that I didn't have to make any major changes in my pipeline and when integrating the front end compared to react-scripts had to be made. I therefore continued to use the structure of the previous build and configured it accordingly in Vite. For example, the bundling of the assets, their naming and the folder structure had to be adapted in order to deliver the build correctly.
Import of SVGs as React components
The SVG was originally imported as follows:
import { ReactComponent as IconClose } from '../assets/icons/shared/close.svg';
However, after switching to Vite, I received the error:
TS2614: Module "*.svg" has no exported member 'ReactComponent'. Did you mean to use 'import ReactComponent from "*.svg"' instead?
By using the svgr-plugins, I can now import the SVG as follows:
import IconClose from '../../assets/icons/shared/close.svg?react';
I have now completed everything on the build side and the application can be started locally and delivered in the pipeline.
Testing
react-scripts was also used to start my tests. A first naive approach was to start the existing tests directly with Jest to be executed, as react-scripts also relies on Jest under the hood. After many error messages and adjustments to configurations without much success, I switched to Vitest set. Although some error messages also occurred here and configuration was required, I found the setup easier.
In the end I have the following vitest.config.js created:
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import svgr from 'vite-plugin-svgr';
export default defineConfig({
plugins: [react(), svgr()],
test: {
environment: 'jsdom',
globals: true,
setupFiles: 'src/setupTests.ts',
},
});
Important points here were the import of svgrso that the SVG components can be imported correctly in the tests. I also use global functions and values that need to be mocked. Therefore I have globals on true and configured a test setup.
In addition, I had to change every place in the code where Jest functions are accessed to vi change. For example:
Before:
setClickable = jest.fn().mockImplementation((flag: boolean): void => { // ... });
After:
setClickable = vi.fn().mockImplementation((flag: boolean): void => { // ... });
After I had made these changes, there was another error with SVGs from a library. These were imported by the tests as React components, but were not exported as such in the library itself. Here I had to create a mock, which was not necessary before, to avoid errors in the tests.
Conclusion:
The changeover to Vite involved a few obstacles, but I was able to solve them in a reasonable amount of time. In total, the migration took about two days. The biggest challenge was integrating the whole thing into an existing project.
With regard to the advantages or disadvantages compared to react-scripts I still lack experience from the project. So far, however, the change has been at least positive-neutral, has had no unplanned consequences and I am now using a modern build tool.



