Skip to main content
  1. Posts/

Porting a Create React App application to Vite Pt. 1: Base Project

·1868 words·9 mins
Project Architecture New Tech configuration NodeJS
Tim Goshinski
Author
Tim Goshinski
I code and I know (some) things
Table of Contents

Why?
#

Vite’s primary value proposition is a faster, more performant developer experience. Vite is powered by Rollup and ESBuild, a JavaScript bundler written in Go, while Create React App leverages the stable, battle tested, combination of WebPack and the Babel transpiler.

So, if I am happy with Create React App then why this port?

  • Evaluate the Hype - I like to try out the new tech and see if it can improve my workflow
  • Pet Peeve - CRA does not give you full control of your Jest configuration, plus it pollutes package.json with the configuration values that you are allowed to specify
  • To do it - I learn a lot from little experiments like this

I am breaking this into two parts in an attempt to keep the information digestible. This first part will focus on getting the application to run in the browser, identical to the CRA version, along with the same quality assurance tooling that mostly comes out of the box with CRA. Part two will focus on unit tests and enforcing test coverage.

As always use your own judgement before pursuing the next-shiny-thing, because for all I know CRA may soon be able to leverage the new TurboPack proposed WebPack successor and re-claim the speed title.

Preflight
#

Create a Clean Shell
#

Let’s begin by scaffolding the new project from Vite’s minimal React + TypeScript template:

yarn create vite vite-redux-seed --template react-ts

cd vite-redux-seed

yarn

» First Commit

Add Static Code Analysis
#

Setting up some basic static analysis tooling is a fairly easy, and inexpensive, way of ensuring basic code quality and consistency. As an added bonus there are plugins for ESLint such as jsx-a11y to alert me when I am inadvertently creating accessibility issues in my application. I highly recommend the typesync command to save time by automatically finding any missing typings for your packages.

yarn add --dev npm-run-all browserslist sass prettier eslint stylelint \
@typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-config-prettier \
eslint-plugin-jsx-a11y eslint-plugin-react eslint-plugin-react-hooks \
stylelint-config-prettier stylelint-config-recommended-scss stylelint-scss \
postcss

# add missing typings to package.json
npx typesync

# install the typings found by typesync
yarn

Now we can pull most of our code-quality configuration files straight over from the old project:

Create React App sets many defaults for ESLint that we will have to explicitly mimic ourselves in a new custom ESLint config file:

file: .eslintrc.json

{
  "root": true,
  "env": {
    "browser": true,
    "node": true
  },
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaVersion": "latest",
    "sourceType": "module"
  },
  "plugins": ["@typescript-eslint", "react", "react-hooks", "jsx-a11y"],
  "extends": [
    "eslint:recommended",
    "plugin:react/recommended",
    "plugin:react-hooks/recommended",
    "plugin:jsx-a11y/recommended",
    "prettier"
  ],
  "rules": {
    "no-magic-numbers": [2, { "ignore": [-1, 0, 1, 2, 10, 100, 1000] }],
    "no-unused-vars": [2, { "vars": "local", "args": "after-used", "argsIgnorePattern": "_" }],
    "no-console": [
      "error",
      {
        "allow": ["error", "info", "warn"]
      }
    ]
  },
  "settings": {
    "react": {
      "version": "detect"
    }
  }
}

Now that our tools are installed and configured let’s artificially create an accessibility error by removing an alt property from our image tag and verify it causes a linting error:

displaying eslint accessibility error by removing `alt` prop from image tag

Linting works

» Second Commit

As we can now verify that the linter is working why don’t we take the next step and explicitly enforce our choices with a git hook. First we will need a couple of packages:

  1. Husky to easily create hooks for this project
  2. Pretty-Quick to automatically apply our predefined code formatting standards
yarn add --dev pretty-quick husky

npx husky install

npm pkg set scripts.prepare="husky install"

I have added a few scripts to help with project maintenance - the two highlighted below will ensure that all code is in our defined format, and that any linting issues will cause an error:

file: package.json

  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview",
    "format": "pretty-quick --staged",
    "format:check": "prettier --check .",
    "format:fix": "prettier --write .",
    "lint": "run-p lint:*",
    "lint:ts": "eslint ./src/**/**.ts*",
    "lint:styles": "stylelint \"./src/**/*.scss\"",
    "fix:styles": "stylelint \"./src/**/*.scss\" --fix",
    "prepare": "husky install"
  },

Once they are added to a pre-commit hook any commit that is attempted with style or code linting errors will automatically be aborted:

npx husky add .husky/pre-commit "npm run format"
npx husky add .husky/pre-commit "npm run lint"

» Third Commit

Porting the Application
#

Let’s first make sure that the project will run, by replacing the contents of the src folder with everything from the vanilla CRA project - excluding tests and snapshots for now. We will need to rename src/index.tsx, Create React App’s default root component, to src/main.tsx which is Vite’s default. Before trying to run it we need to install all of our missing packages:

yarn add @fortawesome/fontawesome-svg-core @fortawesome/free-brands-svg-icons \
@fortawesome/free-regular-svg-icons @fortawesome/free-solid-svg-icons \
@fortawesome/react-fontawesome @reduxjs/toolkit bootstrap @popperjs/core \
react-bootstrap react-redux react-router-dom redux uuid

# add missing typings to package.json
npx typesync

# install the typings found by typesync
yarn

.env Changes
#

I utilized Create React App’s .env file to store a sample third party API URL:

file: .env (old app)

REACT_APP_API_URI=https://jsonplaceholder.typicode.com

It will need to be changed to Vite’s format:

file: .env

VITE_API_URI=https://jsonplaceholder.typicode.com

Let’s also add type information for our custom environment variable(s):

file: /src/env.d.ts

/// <reference types="vite/client" />

interface ImportMetaEnv {
  readonly VITE_API_URI: string;
}

interface ImportMeta {
  readonly env: ImportMetaEnv;
}

And finally update the reference in the service helper:

file: /src/helpers/service.ts

...
const apiUri = process.env.REACT_APP_API_URI;
...

becomes:

file: /src/helpers/service.ts

...
const apiUri = import.meta.env.VITE_API_URI;
...

First Test Run
#

Let’s see how far that has gotten us:

yarn dev
browser displays Sass error, does not understand '~' import

Ew, yuck

Fix Bootstrap’s Sass Import
#

It appears that we need to tell Vite how to resolve ~bootstrap from /src/styles/global.scss:

file: vite.config.ts

import { resolve } from 'path';
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      '~bootstrap': resolve(__dirname, 'node_modules/bootstrap'),
    },
  },
});

But now I have angered WebStorm

WebStorm showing errors for the __dirname variable and `path` import

I missed something again

Lucky for me this is an easy fix by adding the NodeJS typings to the project:

yarn add --dev @types/node

Second Test
#

Now if we refresh or re-run yarn dev the running application looks okay:

screen capture of the users route working in the browser

The application appears to work

Quality Assurance Sweep
#

Better verify what the linter sees:

➜  vite-redux-seed (main) ✗ yarn lint
yarn run v1.22.19
$ run-p lint:*
$ eslint ./src/**/**.ts*
$ stylelint "./src/**/*.scss"

/home/tgoshinski/work/lab/react/vite-redux-seed/src/@enums/AlertTypes.ts
  1:6  error  'AlertTypes' is defined but never used  no-unused-vars
  2:3  error  'Error' is defined but never used       no-unused-vars
  3:3  error  'Info' is defined but never used        no-unused-vars
  4:3  error  'Success' is defined but never used     no-unused-vars
  5:3  error  'Warning' is defined but never used     no-unused-vars

/home/tgoshinski/work/lab/react/vite-redux-seed/src/@enums/AppRoutes.ts
  1:6  error  'AppRoutes' is defined but never used      no-unused-vars
  2:3  error  'Home' is defined but never used           no-unused-vars
  3:3  error  'Users' is defined but never used          no-unused-vars
  4:3  error  'Notifications' is defined but never used  no-unused-vars
  5:3  error  'Fallthrough' is defined but never used    no-unused-vars

/home/tgoshinski/work/lab/react/vite-redux-seed/src/@enums/AsyncStates.ts
  1:6  error  'AsyncStates' is defined but never used  no-unused-vars
  2:3  error  'Idle' is defined but never used         no-unused-vars
  3:3  error  'Pending' is defined but never used      no-unused-vars
  4:3  error  'Success' is defined but never used      no-unused-vars
  5:3  error  'Fail' is defined but never used         no-unused-vars

/home/tgoshinski/work/lab/react/vite-redux-seed/src/@enums/FetchMethods.ts
  1:6  error  'FetchMethods' is defined but never used  no-unused-vars
  2:3  error  'Delete' is defined but never used        no-unused-vars
  3:3  error  'Get' is defined but never used           no-unused-vars
  4:3  error  'Patch' is defined but never used         no-unused-vars
  5:3  error  'Post' is defined but never used          no-unused-vars
  6:3  error  'Put' is defined but never used           no-unused-vars

/home/tgoshinski/work/lab/react/vite-redux-seed/src/@enums/HttpStatusCodes.ts
   2:6  error  'HttpStatusCodes' is defined but never used      no-unused-vars
   3:3  error  'Ok' is defined but never used                   no-unused-vars
   4:3  error  'Created' is defined but never used              no-unused-vars
   5:3  error  'Accepted' is defined but never used             no-unused-vars
   6:3  error  'NoContent' is defined but never used            no-unused-vars
   7:3  error  'MovedPermanently' is defined but never used     no-unused-vars
   8:3  error  'Redirect' is defined but never used             no-unused-vars
   9:3  error  'BadRequest' is defined but never used           no-unused-vars
  10:3  error  'Unauthorized' is defined but never used         no-unused-vars
  11:3  error  'Forbidden' is defined but never used            no-unused-vars
  12:3  error  'NotFound' is defined but never used             no-unused-vars
  13:3  error  'InternalServerError' is defined but never used  no-unused-vars
  14:3  error  'NotImplemented' is defined but never used       no-unused-vars
  15:3  error  'BadGateway' is defined but never used           no-unused-vars

/home/tgoshinski/work/lab/react/vite-redux-seed/src/@enums/ToastTypes.ts
  1:6  error  'ToastTypes' is defined but never used  no-unused-vars
  2:3  error  'Error' is defined but never used       no-unused-vars
  3:3  error  'Info' is defined but never used        no-unused-vars
  4:3  error  'Success' is defined but never used     no-unused-vars
  5:3  error  'Warning' is defined but never used     no-unused-vars

/home/tgoshinski/work/lab/react/vite-redux-seed/src/helpers/service.ts
  23:17  error  'RequestInit' is not defined  no-undef

41 problems (41 errors, 0 warnings)

error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
ERROR: "lint:ts" exited with 1.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

Ouch, that is a lot of errors. One thing I see that I forgot was to extend the ESLint TypeScript plugin’s recommended:

file: .eslintrc.json

{
  "root": true,
  "env": {
    "browser": true,
    "node": true
  },
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaVersion": "latest",
    "sourceType": "module"
  },
  "plugins": ["@typescript-eslint", "react", "react-hooks", "jsx-a11y"],
  "extends": [
    "eslint:recommended",
    "plugin:react/recommended",
    "plugin:react-hooks/recommended",
    "plugin:jsx-a11y/recommended",
    "plugin:@typescript-eslint/recommended",
    "prettier"
  ],
  "rules": {
    "no-magic-numbers": [2, { "ignore": [-1, 0, 1, 2, 10, 100, 1000] }],
    "no-unused-vars": [2, { "vars": "local", "args": "after-used", "argsIgnorePattern": "_" }],
    "no-console": [
      "error",
      {
        "allow": ["error", "info", "warn"]
      }
    ]
  },
  "settings": {
    "react": {
      "version": "detect"
    }
  }
}

Hooray - more errors, not less:

/home/tgoshinski/work/lab/react/vite-redux-seed/src/@interfaces/IApiBaseResponse.ts
  7:10  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any

/home/tgoshinski/work/lab/react/vite-redux-seed/src/helpers/service.ts
  74:66  warning  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any

42 problems (40 errors, 2 warnings)

Personally I choose to turn off no-explicit-any since I do find them useful in very limited circumstances. YMMV but I find that extraneous abuse of any is best called out in code reviews depending on the individual team norms. Next it seems that ESLint’s general no-unused-vars is overriding the TypeScript plugin’s no-unused-vars which is causing all of my enumerations to fail. We should be able to fix these with a couple of simple adjustments to our rules section:

file: .eslintrc.json

{
  "root": true,
  "env": {
    "browser": true,
    "node": true
  },
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaVersion": "latest",
    "sourceType": "module"
  },
  "plugins": ["@typescript-eslint", "react", "react-hooks", "jsx-a11y"],
  "extends": [
    "eslint:recommended",
    "plugin:react/recommended",
    "plugin:react-hooks/recommended",
    "plugin:jsx-a11y/recommended",
    "plugin:@typescript-eslint/recommended",
    "prettier"
  ],
  "rules": {
    "no-magic-numbers": [2, { "ignore": [-1, 0, 1, 2, 10, 100, 1000] }],
    "no-unused-vars": "off",
    "no-console": [
      "error",
      {
        "allow": ["error", "info", "warn"]
      }
    ],
    "@typescript-eslint/no-explicit-any": "off",
    "@typescript-eslint/no-unused-vars": [
      2,
      { "vars": "local", "args": "after-used", "argsIgnorePattern": "_" }
    ]
  },
  "settings": {
    "react": {
      "version": "detect"
    }
  }
}

One more time:

➜  vite-redux-seed (main) ✗ yarn lint
yarn run v1.22.19
$ run-p lint:*
$ stylelint "./src/**/*.scss"
$ eslint ./src/**/**.ts*
Done in 2.79s.

All better.

» Fourth Commit

End of Part One
#

Getting the application to run was not too difficult. It mostly involved reading a little documentation around Vite and around the ESLint settings that CRA hides with its custom plugins. Admittedly I am new to Vite so would appreciate any advice on something you think I could be doing better.