Skip to main content

Migrate to Vite.js

Most of GQC's original batch of React apps utilized create-react-app (CRA) to create the app and thus ended up using react-scripts to bundle and run. CRA hasn't been updated since 2022 and is widely considered to be abandoned. Thus, other technologies need to be used. Vite is a widely accepted successor and is what GQC has chosen to use moving forward.

This document will explain how to upgrade an existing React app to use Vite instead of CRA or Craco (which is only used with our 3D Cesium.js projects).

Walkthrough

  1. Open the CRA project in VS Code. This process will probably be quicker in Linux (WSL) but will also work on Windows, so use whatever you're comfortable with.

  2. Open the package.json file.

    1. Replace the start, build, test, and eject scripts section to use vite:

        "scripts": {
      "start": "vite",
      "build": "vite build",
      "test": "vite test",
      "eject": "vite eject",
      "lint": "eslint --ext .js,.jsx ./src",
      "lint:fix": "eslint --fix --ext .js,.jsx ./src"
      },
    2. In the dependencies section, remove all entries for craco and react-scripts.

    3. Also in the dependencies section, add entries for vite and the cesium plugin if using cesium:

      "dependencies": {
      "vite": "^5.4.1",
      },
      "devDependencies": {
      "vite-plugin-static-copy": "^1.0.6",
      "vite-plugin-cesium": "^1.2.23"
      }
    4. Upgrade assorted modules, but most importantly react. Here is a snip of the latest versions GQC has tested:

      "dependencies": {
      "@arcgis/core": "^4.30.9",
      "@arcgis/map-components-react": "^4.30.7",
      "@emotion/react": "^11.8.1",
      "@emotion/styled": "^11.8.1",
      "@faker-js/faker": "^8.4.1",
      "@iconify/react": "^5.0.2",
      "@mui/icons-material": "^5.5.1",
      "@mui/lab": "^5.0.0-alpha.69",
      "@mui/material": "^5.4.2",
      "@mui/utils": "^5.4.2",
      "@testing-library/jest-dom": "^6.4.8",
      "@vitejs/plugin-react": "^4.3.1",
      "apexcharts": "^3.53.0",
      "cesium": "^1.120.0",
      "change-case": "^5.4.4",
      "date-fns": "^3.6.0",
      "formik": "^2.2.9",
      "framer-motion": "^11.3.28",
      "history": "^5.2.0",
      "lodash": "^4.17.21",
      "numeral": "^2.0.6",
      "ol": "^7.2.2",
      "prop-types": "^15.8.1",
      "react": "^18.3.1",
      "react-apexcharts": "^1.4.1",
      "react-dom": "^18.3.1",
      "react-helmet-async": "^2.0.5",
      "react-router-dom": "^6.2.1",
      "resium": "^1.18.1",
      "simplebar": "^6.2.7",
      "simplebar-react": "^3.2.6",
      "vite": "^5.4.1",
      "web-vitals": "^4.2.3",
      "yup": "^1.4.0"
      },
      "devDependencies": {
      "@babel/core": "^7.17.5",
      "@babel/eslint-parser": "^7.17.0",
      "eslint": "^9.9.0",
      "eslint-config-airbnb": "^19.0.4",
      "eslint-config-prettier": "^9.1.0",
      "eslint-config-react-app": "^7.0.0",
      "eslint-plugin-flowtype": "^8.0.3",
      "eslint-plugin-import": "^2.25.4",
      "eslint-plugin-jsx-a11y": "^6.5.1",
      "eslint-plugin-prettier": "^5.2.1",
      "eslint-plugin-react": "^7.28.0",
      "eslint-plugin-react-hooks": "^4.3.0",
      "prettier": "^3.3.3",
      "vite-plugin-cesium": "^1.2.23",
      "vite-plugin-static-copy": "^1.0.6"
      }
  3. Move the file ./public/index.html to the root directory ./index.html.

  4. Edit the index.html file to remove all instances of %PUBLIC_URL%. Just remove the string, don't replace it with anything.

  5. Use the following python script to rename all *.js files to *.jsx. IMPORTANT specify the src/ directory so you don't convert node_modules:

    import os
    def rename_js_to_jsx(root_dir):
    # Walk through the directory and subdirectories
    for dirpath, _, filenames in os.walk(root_dir):
    for filename in filenames:
    # Check if the file has a .js extension
    if filename.endswith('.js'):
    old_file = os.path.join(dirpath, filename)
    new_file = os.path.join(dirpath, filename[:-3] + '.jsx')
    # Rename the file
    os.rename(old_file, new_file)
    print(f'Renamed: {old_file} -> {new_file}')
    if __name__ == "__main__":
    root_directory = input("Enter the directory path: ")
    if os.path.isdir(root_directory):
    rename_js_to_jsx(root_directory)
    print("Renaming completed.")
    else:
    print(f"The provided path '{root_directory}' is not a valid directory.")
  6. Create a new root-level file called vite.config.mjs with the following content (use the cesium or non-cesium sample appropriately!):

    // If using cesium:
    import { defineConfig } from 'vite';
    import { viteStaticCopy } from 'vite-plugin-static-copy';
    import react from '@vitejs/plugin-react';
    import cesium from 'vite-plugin-cesium';
    const cesiumSource = 'node_modules/cesium/Build';
    // This is the base url for static files that CesiumJS needs to load.
    // Set to an empty string to place the files at the site's root path
    const cesiumBaseUrl = 'cesium';
    export default defineConfig({
    define: {
    // Define relative base path in cesium for loading assets
    // https://vitejs.dev/config/shared-options.html#define
    CESIUM_BASE_URL: JSON.stringify(`/${cesiumBaseUrl}`),
    },
    // depending on your application, base can also be '/'
    base: '/',
    plugins: [
    react(),
    cesium(),
    viteStaticCopy({
    targets: [
    { src: `${cesiumSource}/ThirdParty`, dest: cesiumBaseUrl },
    { src: `${cesiumSource}/Workers`, dest: cesiumBaseUrl },
    { src: `${cesiumSource}/Assets`, dest: cesiumBaseUrl },
    { src: `${cesiumSource}/Widgets`, dest: cesiumBaseUrl },
    ],
    })
    ],
    server: {
    // this ensures that the browser opens upon server start
    open: true,
    // this sets a default port to 3000
    port: 3000
    }
    });
    // NOT using cesium:
    import { defineConfig } from 'vite';
    import react from '@vitejs/plugin-react';
    // This is the base url for static files that CesiumJS needs to load.
    // Set to an empty string to place the files at the site's root path
    export default defineConfig({
    // depending on your application, base can also be '/'
    base: '/',
    plugins: [react()],
    server: {
    // this ensures that the browser opens upon server start
    open: true,
    // this sets a default port to 3000
    port: 3000
    }
    });
  7. Search for all instances of process.env in *.jsx files and replace them with import.meta.env.

  8. Modify ./src/index.jsx to use new react-dom standard:

    import 'simplebar-react/dist/simplebar.min.css';

    import { createRoot } from 'react-dom/client';
    import { BrowserRouter } from 'react-router-dom';
    import { HelmetProvider } from 'react-helmet-async';

    //
    import App from './App';
    import * as serviceWorker from './serviceWorker';
    import reportWebVitals from './reportWebVitals';
    import { CustomContextProvider } from './contexts/CustomContext';

    // ----------------------------------------------------------------------

    const rootElement = document.getElementById('root');
    const root = createRoot(rootElement);

    root.render(
    <CustomContextProvider>
    <HelmetProvider>
    <BrowserRouter>
    <App />
    </BrowserRouter>
    </HelmetProvider>
    </CustomContextProvider>
    );

    // If you want to enable client cache, register instead.
    serviceWorker.unregister();

    // If you want to start measuring performance in your app, pass a function
    // to log results (for example: reportWebVitals(console.log))
    // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
    reportWebVitals();
  9. Modify BaseOptionChart.jsx. Look for the final section in the options file mentioned, responsive: [, and comment it (the entire section) out. For some reason, the specified breakpoints (which only apply to bar charts) cause infinite re-renders.