Vite: Powerful React Project Setup
Tags: react, vscode, vite, testing
Published: 2022-05-07 01:04:26

Language: [🇪🇸] [Español](https://dev.to/equimancho/vite-configuracion-para-react-3hf2) - [🇺🇸] [English](https://dev.to/equiman/vite-"werful-react-project-g4m)

---

[Vite](https://vitejs.dev/) is by far the best alternative for creating React projects today.

```bash
npm create vite@latest <project-name> -- --template <react-swc|react-swc-ts>
# npm 7+, extra double-dash is needed

cd <project-name>
npm install
npm run dev
```

With these commands, we create a very basic and clean project to serve as a starting point, but it will gonna need some extra tools to automate tasks that can make your life and the life of your development team easier.

---

# VSCode

It's recommended to make these configurations on **project settings** and not in the global settings.

We will start creating a `.vscode` folder with a `settings.json` file inside.

```json
# 📄 File: /.vscode/settings.json
-----------------------------------

{
    "explorer.fileNesting.patterns": {
        "*.js": "$(capture).js.map, $(capture).*.js, $(capture)_*.js, $(capture)*.snap",
        "*.jsx": "$(capture).js, $(capture).*.jsx, $(capture)_*.js, $(capture)_*.jsx, $(capture)*.snap",
        "*.ts": "$(capture).js, $(capture).d.ts.map, $(capture).*.ts, $(capture)_*.js, $(capture)_*.ts, $(capture)*.snap",
        "*.tsx": "$(capture).ts, $(capture).*.tsx, $(capture)_*.ts, $(capture)_*.tsx, $(capture)*.snap",
    },
    "emmet.excludeLanguages": [],
    "emmet.includeLanguages": {
        "markdown": "html",
        "javascript": "javascriptreact",
        "typescript": "typescriptreact"
    },
    "emmet.showSuggestionsAsSnippets": true,
    "emmet.triggerExpansionOnTab": true,
    "files.exclude": {
        "**/*.js.map": {
            "when": "$(basename)"
        },
        "**/node_modules": true,
    },
    "html.autoClosingTags": true,
    "javascript.autoClosingTags": true,
    "javascript.suggest.completeFunctionCalls": true,
    "typescript.suggest.completeFunctionCalls": true,
    "javascript.inlayHints.functionLikeReturnTypes.enabled": true,
    "typescript.inlayHints.functionLikeReturnTypes.enabled": true,
    "javascript.inlayHints.parameterNames.enabled": "all",
    "typescript.inlayHints.parameterNames.enabled": "all",
    "javascript.suggest.autoImports": true,
    "search.exclude": {
        "**/coverage": true,
        "**/node_modules": true
    },
    "typescript.autoClosingTags": true,
    "typescript.suggest.autoImports": true,
}
```
  
> There are a lot of VSCode extensions and configurations out there. If you are hungry for more check [VSCode - Essentials](https://dev.to/equiman/my-essential-visual-studio-code-extensions-and-configurations-5197) and [VSCode - React Flavored](https://dev.to/equiman/vscode-react-flavored-134h).

## Debugging

There is no need to install an extra extension for debug React from VSCode. 

```json
# 📄 File: /.vscode/launch.json
-----------------------------------

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "chrome", //or "msedge"
            "request": "launch",
            "name": "Launch Chrome against localhost",
            "url": "http://localhost:3000",
            "webRoot": "${workspaceFolder}"
        }
    ]
}
```

If you want to disable the browser opening each time add `BROWSER=none` on `.env` file in your project. Also change the dev script to use the same port.

```diff
# 📄 File: package.json
-----------------------------------

{
  "scripts": {
-   "dev": "vite",
+   "dev": "vite --port 3000",
  }
}
```

Run the `npm run dev` command and start the browser from the `Run and Debug panel`. Now you can add break points directly on the VSCode.

Or if you only need a quick verification use the Simple Browser inside VSCode.

{% post https://dev.to/equiman/vscode-browser-inside-2b06 %}

## Extensions

If you want that VSCode show extensions recommendation when open the project

```json
# 📄 File: /.vscode/extentions.json
-----------------------------------

{
    "recommendations": [
        "dbaeumer.vscode-eslint",
        "esbenp.prettier-vscode",
        "stylelint.vscode-stylelint",
        "ZixuanChen.vitest-explorer"
    ]
}
```

---

## Linter

- [ES Lint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) extension

```diff
# 📄 File: /.vscode/settings.json
-----------------------------------

{ 
+    "editor.formatOnSave": true,
+    "javascript.format.enable": false,
+    "javascript.validate.enable": true,
+    "typescript.format.enable": false,
+    "typescript.validate.enable": true,
+    "editor.codeActionsOnSave": {
+        "source.fixAll.eslint": true
+    }
}
```
  
Install and config on the project folder:

```bash
npm install -D eslint
npm init @eslint/config
```

You can choose the better for you, but my opinionated configurations are:

```bash
Use: To check syntax, find problems, and enforce code style
Type of modules: JavaScript modules (import/export)
Framework: React
Typescript: No #or Yes if the project uses it
Run: Browser #and Node if use Next.js
Style guide: Popular -> Standard #JS without semi-colon ;
Format: JSON
```

> **Semi-colon exception**  
> Because the `standard` style guide does not use semi-colons (`;`) take this in mind. If the next statement right after the line without a semicolon starts with one of the following special operators `[`, `(`, `+`, `\`*, `/`, `-`, `,`, `.`, it will be recognized as an expression to the previous statement. Then you are going to need to start the line with `;`.

You will be asked to install extra packages. Answer **yes**. When finish update configurations `rules`:

```diff
# 📄 File: .eslintrc.json
-----------------------------------

{
    "rules": {
+     "no-console": "warn",
+     "react/prop-types": "off",
+     "react/self-closing-comp": "warn",
+     "react/react-in-jsx-scope": "off"
    },
+   "settings": {
+     "react": {
+       "version": "detect"
+     }
+   }
}
```

If you are using `TypeScript` also need to add this configuration:

```diff
# 📄 File: .eslintrc.json
-----------------------------------

{
    "parserOptions": {
+     "project": ["tsconfig.json"],
+     "createDefaultProgram": true
    },
    "rules": {
      "no-console": "warn",
      "react/prop-types": "off",
      "react/self-closing-comp": "warn",
+     "@typescript-eslint/consistent-type-definitions": ["error", "type"],
+     "@typescript-eslint/explicit-function-return-type": "off",
  },
}
```

Create a `.eslintignore` file on the root of the folder project:

```config
# 📄 File: .eslintignore
-----------------------------------

build
coverage
dist
```

> There is no need to add `node_modules` because it was ignored by default.
  
If you don't want to use `ES Lint` extensions, add `list` and `fix` command *at end* of `scripts`:

```diff
# 📄 File: package.json
-----------------------------------

{
  "scripts": {
+    "lint": "eslint .",
+    "lint:fix": "eslint . --fix --ext .js,.jsx,.ts,.tsx"
  }
}
```

### Avoid import React error

> Since React 17, you don't need to `import React` to use JSX anymore. But we would still need to import React to use Hooks or other exports that React provides.

To avoid `ES Lint` warns about import React, add a plugin:

```diff
# 📄 File: .eslintrc.json
-----------------------------------

{
    "extends": [
      "plugin:react/recommended",
      "standard-with-typescript",
+     "plugin:react/jsx-runtime",    
    ],
}
```

### Blank Lines

If you want to preserve a blank line's previous definitions.

```diff
# 📄 File: .eslintrc.json
-----------------------------------

{
    "rules": {
+       "padding-line-between-statements": [
+           "error",
+           {
+               "blankLine": "always",
+               "prev": "*",
+               "next": "return"
+           },
+           {
+               "blankLine": "always",
+               "prev": [
+                   "const",
+                   "let",
+                   "var"
+               ],
+               "next": "*"
+           },
+           {
+               "blankLine": "any",
+               "prev": [
+                   "const",
+                   "let",
+                   "var"
+               ],
+               "next": [
+                   "const",
+                   "let",
+                   "var"
+               ]
+           }
+       ]
    },
}
```

### Auto sort

If you don't want to deal with sorting imports and properties set this configuration.

```diff
# 📄 File: .eslintrc.json
-----------------------------------

{
    "rules": {
+       "import/order": [
+           "warn",
+           {
+               "pathGroups": [
+                   {
+                       "pattern": "~/**",
+                       "group": "external",
+                       "position": "after"
+                   }
+               ],
+               "newlines-between": "always-and-inside-groups"
+           }
+       ],
+       "react/jsx-sort-props": [
+           "warn",
+           {
+               "callbacksLast": true,
+               "shorthandFirst": true,
+               "noSortAlphabetically": false,
+               "reservedFirst": true
+           }
+       ]
    },
}
```

---

## Format

> `ES Lint` can be enough, and Prettier it's optional because have a better performance formatting than `ES Lint`. If you want to use it go ahead. But there is a problem, they fight to try both to format code, so you have to know how to configure them to work together.

If you want to use it go ahead.

- [Prettier - Code formatter](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) extension

```diff
# 📄 File: /.vscode/settings.json
-----------------------------------

{
-    "editor.codeActionsOnSave": {
-        "source.fixAll.eslint": true
-    }
+    "eslint.probe": [
+        "javascript",
+        "javascriptreact",
+        "typescript",
+        "typescriptreact"
+    ],
+    "[javascript][typescript]": {
+        "editor.defaultFormatter": "esbenp.prettier-vscode",
+        "editor.formatOnSave": false,
         // Runs Prettier, then ESLint
+        "editor.codeActionsOnSave": [
+            "source.formatDocument",
+            "source.fixAll.eslint"
+        ],
+    }
}
```
  
Install Prettier:

```bash
npm install -D prettier 
```

And ESLint (JS) for prettier:

```bash
npm install -D eslint-config-prettier
```

Or TSLint (TS) for prettier:

```bash
npm install -D tslint-config-prettier
```

Create a `.prettierignore` file on the root of the folder project:

```config
# 📄 File: .prettierignore
-----------------------------------

build
coverage
dist
package-lock.json
```

> There is no need to add `node_modules` because it was ignored by default.

Create a `.prettierrc.json` file on the root of the folder project:

```json
# 📄 File: .prettierrc.json
-----------------------------------

{
    "semi": false,
    "singleQuote": true
}
```

Add extends prettier configuration *at the end* of `extends`:

```diff
# 📄 File: .eslintrc.json
---

{
    "extends": [
        "plugin:react/recommended",
        "standard-with-typescript",
        "plugin:react/jsx-runtime",
+       "prettier", //at last
    ],
}
```
  
If you don't want to use prettier extensions, add `check` and `write` command *at end* of `scripts`:

```diff
# 📄 File: package.json
-----------------------------------

{
  "scripts": {
+    "format": "prettier . --check",
+    "format:fix": "prettier . --write"
  }
}
```

---

## HTML Linter

- [HTMLHint](https://marketplace.visualstudio.com/items?itemName=HTMLHint.vscode-htmlhint)

```bash
npm install -D htmlhint
```

If you also want to lint HTML with `eslint` install this additional plugin:

```bash
npm install -D eslint-plugin-html
```

And add `html` to the list of plugins

```diff
# 📄 File: .eslintrc.json
-----------------------------------

{
    "plugins": [
        "react",
+       "html"
    ],
}
```

---

## CSS Linter

- [Stylelint](https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint) extension

Install and config on the project folder:

```bash
npm install -D stylelint stylelint-config-standard stylelint-config-idiomatic-order
```

Create a configuration file named `.stylelintrc.json` in the top level of your repository.

```json
# 📄 File: .stylelintrc.json
-----------------------------------

{
  "extends": [
    "stylelint-config-standard",
    "stylelint-config-idiomatic-order"
  ],
  "rules": {
    "declaration-colon-newline-after": "always-multi-line"
  },
  "ignoreFiles": [
    "build/**",
    "coverage/**",
    "dist/**",
    "**/*.js",
    "**/*.jsx", 
    "**/*.ts",
    "**/*.tsx" 
  ] 
}
```

If you are going to use `styled components` also install

```bash
npm install -D stylelint-config-styled-components
```

```diff
# 📄 File: .stylelintrc.json
-----------------------------------

{
  "extends": [
    "stylelint-config-standard",
    "stylelint-config-idiomatic-order",
+   "stylelint-config-styled-components"
  ],
  "ignoreFiles": [
    "build/**",
    "coverage/**",
    "dist/**",
    "**/*.js",
-   "**/*.jsx", 
    "**/*.ts",
-   "**/*.tsx" 
  ] 
}
```

To prevent both VS Code's built-in linters and Stylelint from reporting the same errors, you can disable the built-in linters.

```diff
# 📄 File: /.vscode/settings.json
-----------------------------------

{ 
+    "stylelint.enable": true,
+    "css.validate": false,
+    "less.validate": false,
+    "scss.validate": false,
+    "[css][scss]": {
+        "editor.defaultFormatter": "stylelint.vscode-stylelint",
+        "editor.codeActionsOnSave": [
+            "source.fixAll.stylelint"
+        ],
+    }
}
```

Stylelint has more than 170ish rules. Sometimes it will show you an error that will literally cause a problem.

---

## Git Linter

If the project doesn't have a git repository. First, run:

```bash
git init
```

- [Lint Staged](https://github.com/okonet/lint-staged)

It works over [Husky](https://github.com/typicode/husky) and only runs linters against staged git files. By doing so you can ensure no errors go into the repository and enforce code style.

Install and config on the project folder:

```bash
npx mrm@3 lint-staged
```

---

## Testing

Use [Vitest](https://vitest.dev/). It's compatible with Jest language API so you don't need to relearn the syntax.

```bash
npm install -D vitest
```
  
With the `global` flag you don't need to import de dependencies on each file adding automatic support to Jest.

```diff
# 📄 File: vitest.config.ts
-----------------------------------

- import { defineConfig } from 'vite'
+ import { defineConfig } from 'vitest/config'

export default defineConfig({
+  test: {
+    globals: true,
+  },
})
```

If you are using typescript, also add this configuration.

```diff
# 📄 File: tsconfig.json
-----------------------------------

{ 
+  "compilerOptions": {
+    "types": ["vitest/globals"],
+  }
}
```

The next step, it's not required. But if you want to take advantage of IntelliSense it's recommended to start the test files with:

```javascript
# 📄 Files: *.test.js
-----------------------------------

import { it, expect, describe } from "vitest";
```

Update run test scripts like this:

```diff
# 📄 File: package.json
-----------------------------------

{
  "scripts": {
+    "test":    "vitest --run --reporter verbose",
+    "test:w":  "vitest",
+    "test:ui": "vitest --ui",
  }
}
```

### Snapshot

If you want the snapshots to be located at the same level as test files instead of `__snapshots__` folder. Add this property.

```diff
# 📄 File: vitest.config.ts
-----------------------------------

export default defineConfig({
   test: {
     globals: true,
+    resolveSnapshotPath: (testPath, snapExtension) => testPath + snapExtension,
   },
})
```

### Coverage

For coverage reports, we need to install `@vitest/coverage-v8`

```bash
npm install -D @vitest/coverage-v8
```

Update run test scripts like this:

```diff
# 📄 File: package.json
-----------------------------------

{
  "scripts": {
+    "test:c":  "vitest run --coverage",
+    "test:cw": "vitest watch --coverage"
  }
}
```

Add the `@vitest/coverage-v8` configuration.

```diff
# 📄 File: vitest.config.ts
-----------------------------------

import { defineConfig } from 'vitest/config'

export default defineConfig({
   test: {
     globals: true,
+    reporters: ['verbose'],
+    coverage: {
+     all: true,
+      reporter: ['text', 'html', 'lcov'],
+      include: ['**/src/**/*.{js,jsx,ts,tsx}'],
+      exclude: [
+        '**/src/main.{js,jsx,ts,tsx}',
+        '**/*.types.{ts,tsx}',
+        '**/*.test.{js,jsx,ts,tsx}',
+        '**/src/vite-env*',
+      ],
+      statements: 0,
+      branches: 0,
+      functions: 0,
+      lines: 0,
+    },
  },
})
```

### RTL (React Testing Library)

Run this command to install `RTL`.

```bash
npm install -D @testing-library/react @testing-library/dom @testing-library/user-event @testing-library/jest-dom
```

Also need to install `jsdom` (or `happy-dom`)

```bash
npm install -D jsdom
```

And add it as `environment`

```diff
# 📄 File: vitest.config.ts
-----------------------------------

import { defineConfig } from 'vitest/config'

export default defineConfig({
   test: {
     globals: true,
+    environment: 'jsdom',
   },
})
```

With `setupFiles` properties we can extend the `jest-dom` matchers without importing them on each test file.

```diff
# 📄 File: .vitest/setup.ts
-----------------------------------

+ /* Extend Matchers */
+ import '@testing-library/jest-dom'
```

```diff
# 📄 File: vitest.config.ts
-----------------------------------

import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    globals: true,
+   setupFiles: '.vitest/setup.ts',
  },
})
```

If you are using `eslint` it's good idea to add the RTL linters with this command:

```bash
npm install --D eslint-plugin-testing-library eslint-plugin-jest-dom
```

And add these configurations on the `eslint` configuration file.

```diff
# 📄 File: .eslintrc.json
-----------------------------------

{
    "extends": [
        "plugin:react/recommended",
        "standard-with-typescript",
        "plugin:react/jsx-runtime",
        "prettier",
+       "plugin:testing-library/react",
+       "plugin:jest-dom/recommended"
    ],
    "plugins": [
        "react",
        "html",
+       "testing-library",
+       "jest-dom"
    ],
    "rules": {
+       "testing-library/await-async-query": "error",
+       "testing-library/no-await-sync-query": "error",
+       "testing-library/no-debugging-utils": "warn",
+       "testing-library/no-dom-import": "off",
+       "jest-dom/prefer-checked": "error",
+       "jest-dom/prefer-enabled-disabled": "error",
+       "jest-dom/prefer-required": "error",
+       "jest-dom/prefer-to-have-attribute": "error"
    },
}
```

Add into `setupFiles` a configuration to avoid the global matcher and `jest-dom` collisions.

```diff
# 📄 File: .vitest/setup.ts
-----------------------------------

/* Extend Matchers */
import '@testing-library/jest-dom'
+ import '@testing-library/jest-dom/extend-expect'
```

### Mock Service Worker

If you also want to use [msw](https://mswjs.io/) to test HTTP requests you need to run this command.

```bash
npm install -D msw cross-fetch
``` 

And add this configuration to the global setup.

```diff
# 📄 File: .vitest/setup.ts
-----------------------------------

+ /* Mock Service Worker */
+ import { afterAll, afterEach, beforeAll } from 'vitest'
+ import { fetch } from 'cross-fetch'
+ import { server } from './mocks/server'

+ // Add `fetch` polyfill.
+ global.fetch = fetch

+ // Establish API mocking before all tests
+ beforeAll(() => server.listen({ onUnhandledRequest: `error` }))

+ // Reset any request handlers that we may add during the tests,
+ // so they don't affect other tests
+ afterEach(() => server.resetHandlers())

+ // Clean up after the tests are finished
+ afterAll(() => server. Close())
```

### Debug

For visual debugging tests.

- [Vitest](https://marketplace.visualstudio.com/items?itemName=ZixuanChen.vitest-explorer)  extension
 
---

## Debug

- [clic-to-component](https://github.com/ericclemmons/click-to-component) ⭐

It's not an extension. It is an `npm` package to install on your project that helps with debugging process.

```bash
npm install -S click-to-react-component
```

Even though `click-to-react-component` is added to `dependencies`, [tree-shaking](https://esbuild.github.io/api/#tree-shaking) will remove `click-to-react-component` from `production` builds.

![beam me up](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/clcw1jzihrlwzx3dfq7t.gif)

Using the `alt` key (or `option` in macOS) and with a combination of keys and clicking over the component in the browser, it will **transport** you to the source component in your editor.

![clic-to-component](https://github.com/ericclemmons/click-to-component/raw/main/.github/vite.gif)

Works with `vite` adding these configurations on your project:

```diff
# 📄 File: /src/main.jsx
-----------------------------------

import React from "react"
import ReactDOM from "react-dom/client"
+import { ClickToComponent } from "click-to-react-component"

import App from "./App"
import "./index.css"

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <App />
+   <ClickToComponent />
  </React.StrictMode>
)
```

> ⚠ Sadly, it is not compatible with WSL, but you can use another alternative: [ZSH + Oh My ZSH! on Windows without WSL](https://dev.to/equiman/zsh-on-windows-without-wsl-4ah9)

---

## Styles

### PostCSS

- [PostCSS](https://postcss.org/)

I actually prefer the `PostCSS` approach instead of `Sass` because it is fast, is closer to the CSS standard, and no need for the file extension rename. So in the future, you can get rid of `PostCSS` and your project will continue working without needing a major migration.

```bash
npm install -D postcss-cli postcss
```

For me these are the essentials plugins to install:

```bash
npm install -D @csstools/postcss-nesting-experimental autoprefixer
```

> Install `Autoprefixer` only if you need old browser support.  There are a lot of `PostCSS` plugins, try to not install too many or very rare plugins that are not closer to CSS standards (or proposal).

Create a `postcss.config.cjs` file on the root of the folder project:

```json
# 📄 File: postcss.config.cjs
-----------------------------------

module.exports = {
    "plugins": {
        "@csstools/postcss-nesting-experimental": true,
        "autoprefixer": true
    }
}
```

> Plugin order is important because we need that `nesting` runs before `autoprefixer`

If you decided to use Stylelint. Add this package: 

```bash
npm install --D postcss-syntax
```

Add it as `customSyntax` on `.stylelintrc.json` file 

```diff
# 📄 File: .stylelintrc.json
-----------------------------------

{
  "extends": [
    "stylelint-config-standard"
  ],
+ "customSyntax": "postcss-syntax",
}
```

### Sass

- [Sass](https://sass-lang.com/)

- Extension [Sass](https://marketplace.visualstudio.com/items?itemName=Syler.sass-indented)

If instead of PostCSS you want still to use Sass. Vite has `SCSS` built support. Install this package:

```bash
npm install -D sass
```

> Take care of using `sass` and not `node-sass` package because is deprecated.

If you decided to use Stylelint. Replace these packages: 

```bash
npm remove stylelint-config-standard
npm install --D stylelint-config-standard-scss
```

Replace it as `extends` on `.stylelintrc.json` file 

```diff
# 📄 File: .stylelintrc.json
-----------------------------------

{
  "extends": [
-    "stylelint-config-standard",
+    "stylelint-config-standard-scss"
  ]
}
```

Now rename manually all `.css` files to `.scss` and update `src/App.js` to import `src/App.scss`.

---

## Deploy

If you are going to deploy the project on GitHub pages without using a custom domain you need to change some paths.

```diff
# 📄 File: vite.config.js
-----------------------------------

export default defineConfig({
  plugins: [react()],
+ base: "./",
+ build: {
+   outDir: './docs'
+ }
})
```

---

**That’s All Folks!
Happy Coding** 🖖

[![beer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mkgsx4aw6qn9i9ax6as9.png)](https://github.com/sponsors/deinsoftware)