Skip to content

Technical Guides ‐ Migrating to Vite

Jonathan Sharpe edited this page Oct 6, 2024 · 21 revisions

As explained in ADR-0002, we are migrating our Create React App (CRA)-based client applications over to Vite (and Vitest to replace CRA-managed Jest).

⚠️ If you have any problems migrating your app to Vite while following this guide, report them in #cyf-tech-products.

Prerequisites

Since Vite 5:

Vite no longer supports Node.js 14 / 16 / 17 / 19, which reached its EOL. Node.js 18 / 20+ is now required.

Given ADR-0001 we should be moving all Tech Products repos to Node ^20.9; ensure this is done before starting the Vite migration.

Dependencies

Uninstallation

Remove the old tooling with:

# npm
npm uninstall react-scripts

# yarn
yarn remove react-scripts

Review the dependency list - if you have anything referring to jest (except @testing-library/jest-dom, which is still compatible) it will need to be removed and/or updated.

Installation

# npm
npm install --save-dev vite @vitejs/plugin-react-swc

# yarn
yarn add --dev vite @vitejs/plugin-react-swc

Note that Vite goes in devDependencies, unlike react-scripts which encouraged putting everything in dependencies. Take this opportunity to review what's in each dependency group, and move (i.e. npm uninstall/yarn remove then re-npm install/yarn add) anything that's in the wrong place (e.g. linting- or testing-related dependencies should be --save-dev'd).

Basic configuration

  • Vite uses ESM by default, so set "type": "module" in your package.json.

  • Create a new file vite.config.js and add the following:

    import react from '@vitejs/plugin-react-swc'
    import { defineConfig } from 'vite'
    
    export default defineConfig({
      build: {
        outDir: 'build'
      },
      plugins: [react()]
    })
  • Move index.html out of public/ to the root of the package (i.e. as a sibling of package.json and src/) and add a new line <script type="module" src="/src/main.jsx"></script> in the body (just after <div id="root"></div>).

  • Delete src/serviceWorker.js and remove any reference to it in src/index.js

  • Rename src/index.js to src/main.jsx (note this file extension is now required, see JSX)

Scripts

The Vite CLI supports many of the same commands as react-scripts, but with a few minor changes:

  • Build (compile to static JS/HTML/CSS/etc.):

    -    "build": "react-scripts build",
    +    "build": "vite build",

    Some of our apps have various steps to remove source maps; Vite defaults to not generating source maps at all, this can be configured using the build.sourcemap property in vite.config.js.

  • Eject (stop using CRA and emit Webpack/Babel/etc. code) has no equivalent, just remove it:

    -    "eject": "react-scripts eject",
  • Preview/serve (statically serve the build outputs):

    -    "serve": "serve --single build",
    +    "serve": "vite preview",

    This defaults to port 4173, but we can set preview.port in vite.config.js as needed.

  • Start (dev mode):

    -    "start": "react-scripts start",
    +    "start": "vite",

    This defaults to port 5173 rather than 3000, but we can set server.port in vite.config.js to e.g. parseInt(process.env.PORT ?? "3000", 10) to restore the behaviour we're used to.

  • Test (run low-level tests):

    -    "test": "react-scripts test",
    +    "test": "vitest",

JSX

Unlike CRA, which would transpile JSX in any file with the extension .js or .jsx, Vite requires any file containing JSX to be explicitly .jsx.

Environment variables

Unlike CRA's convention of process.env.REACT_APP_{whatever}*, Vite injects relevant environment variables based on the VITE_ prefix into import.meta.env. Different configuration for different environments can be handled with .env.[mode] files - note that anything secret should be placed in a .env.local/.env.[mode].local file.

Tests

Vitest is API compatible with Jest, which means it's largely a drop-in replacement. Install the following test-specific dependencies:

# npm
npm install --save-dev vitest jsdom

# yarn
yarn add --dev vitest jsdom

Add the following section to the vite.config.js configuration file:

- import { defineConfig } from 'vite'
+ import { defineConfig } from 'vitest/config'
  test: {
    environment: "jsdom",
    globals: true,
  },

If the src/setupTests.js file is in use, add that too:

    test: {
      environment: "jsdom",
      globals: true,
+     setupFiles: [
+       "./src/setupTests.js",
+     ],
    },

Most of the globals (e.g. describe, expect, it) have the same names, but any references to properties of the Jest object, e.g. jest.fn(), need to be replaced with the equivalent from the vi helper, e.g. vi.fn().

Any additional configuration under the "jest" property in package.json will also have to be reviewed and possibly migrated.

SASS

Vite does support CSS pre-processors, however several of our repos are using the now-deprecated node-sass. Instead, to continue using .sass/.scss styles, we need to switch to sass:

# npm
npm uninstall node-sass
npm install --save-dev sass

# yarn
yarn remove node-sass
yarn add --dev sass

No further configuration is needed, Vite picks this up automatically if you import any relevant style files (as CRA did).

Linting

CRA bundled an ESLint configuration, which was likely in use. You may also have had other configs or plugins installed, and/or rules enabled - make note of these. ESLint configuration will definitely be in the eslintConfig field in package.json, but you may also have .eslintrc files.

Instead of the CRA config we can use the configuration provided by the Vite React template by removing eslintConfig from package.json and creating a .eslintrc.cjs in the root of the package containing:

module.exports = {
  root: true,
  env: { browser: true, es2020: true },
  extends: [
    'eslint:recommended',
    'plugin:react/recommended',
    'plugin:react/jsx-runtime',
    'plugin:react-hooks/recommended',
  ],
  ignorePatterns: ['build', '.eslintrc.cjs'],
  parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
  settings: { react: { version: '18.2' } },
  plugins: ['react-refresh'],
  rules: {
    'react-refresh/only-export-components': [
      'warn',
      { allowConstantExport: true },
    ],
  },
}

This requires the following dependencies:

# npm
npm install --save-dev eslint@8 eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-react-refresh

# yarn
yarn add --dev eslint@^8.57.0 eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-react-refresh

Update your linting script in package.json to explicitly include both .js and .jsx files, for example:

-     "lint": "eslint src",
+     "lint": "eslint --ext js,jsx src",

Once all of the linting has been set up (including the optional steps below, if relevant), run npm run lint/yarn lint and fix any issues so we don't break the pipeline (you can apply some fixes automatically with npm run lint -- --fix/yarn lint --fix, others may require a bit more work).

Optional - additional CRA rules

To restore the best of what CRA included, you can then also add:

# npm
npm install --save-dev eslint-plugin-import eslint-plugin-jsx-a11y

# yarn
yarn add --dev eslint-plugin-import eslint-plugin-jsx-a11y

and add the following presets and rules:

    extends: [
      'eslint:recommended',
      'plugin:react/recommended',
      'plugin:react/jsx-runtime',
      'plugin:react-hooks/recommended',
+     'plugin:jsx-a11y/recommended',
    ],
-   plugins: ['react-refresh'],
+   plugins: ['import', 'react-refresh'],
    rules: {
+     'import/first': 'error',
+     'import/no-amd': 'error',
+     'import/no-anonymous-default-export': 'warn',
+     'import/no-webpack-loader-syntax': 'error',
      'react-refresh/only-export-components': [
        'warn',
        { allowConstantExport: true },
      ],
    },

Optional - additional CYF rules

CYF also has its own ESLint configuration you could adopt at this point, by installing:

# npm
npm install --save-dev @codeyourfuture/eslint-config-standard

# yarn
yarn add --dev @codeyourfuture/eslint-config-standard

and switching out the first preset:

    extends: [
-     'eslint:recommended',
+     '@codeyourfuture/standard',
      'plugin:react/recommended',
      'plugin:react/jsx-runtime',
      'plugin:react-hooks/recommended',
      'plugin:jsx-a11y/recommended',
    ],

Proxy

Migrate from CRA's proxy setup (src/setupProxy.js/proxy in package.json) to Vite's proxy setup. Uninstall http-proxy-middleware if present:

# npm
npm uninstall http-proxy-middleware

# yarn
yarn remove http-proxy-middleware

Miscellaneous

  • Remove browserslist from package.json (Vite manages its own supported browsers).
  • Update any documentation (README, wiki, etc.) referring to CRA or Jest

References

* this is achieved by literal text replacement, as process.env doesn't exist in the browser - see e.g. https://stackoverflow.com/a/76440819/3001761 for details

Clone this wiki locally