Discover the best boilerplate for your React projects, make your project scalable

In this article I will show you the best way to build your React projects using Vite, Typescript, ESLint, Prettier, SCSS, Husky, Commitlint, Jest, RTL, etc.

Bryan Aguilar
13 min readJan 3, 2023

Es muy común que cuando inicias un nuevo proyecto, quieras implementar las mejores prácticas, mantener tu código limpio y generar un desarrollo escalable, en este artículo te doy algunas recomendaciones de como lograr algo así.

Puedes encontrar este toda esta estructura en este repositorio de Github. Para este artículo he usado React JS en su versión 18.2.0 con node en su versión 18.12.1

🚧 A tener en cuenta: Puedes conocer la versión de React con el comando

npm view react version

Configuración del proyecto

Comencemos creando un proyecto de React JS, usaremos Vite (es una herramienta de compilación que tiene como objetivo proporcionar una experiencia de desarrollo más rápida y ágil para proyectos web modernos). Desde la consola usaremos el siguiente comando, además para este artículo usaré el gestor de paquetesyarn

yarn create vite

Al ejecutar este comando, te solicitará el nombre de tu proyecto, el framework o librería (React) y una variante (Typescript + SWC), el proyecto se creará rápidamente, cuando finalice, debes ingresar al directorio del proyecto e instalar las dependencias:

cd <project-name>
yarn
yarn dev

Deberías ver la aplicación de demostración disponible:

Bloqueo del motor de Node y gestor de paquetes

Es importante que los desarrolladores que trabajen en tu proyecto utilicen el mismo motor Node y gestor de paquetes. Para ello, debes dos nuevos archivos en la raíz de nuestro proyecto:

  • .nvmrc Le dirá a otros usuarios del proyecto qué versión de Node debe utilizar
  • .npmrc Indicará a otros usuarios del proyecto qué gestor de paquetes se debe utilizar

Para nuestro proyecto:

.nvmrc

v18

.npmrc

engine-strict=true

Puede comprobar su versión de Node con

node -v

Tenga en cuenta que el uso de engine-stric y la creación de estos archivos por si sola no es suficiente, debemos hacer una configuración en el archivo package.json

{
"name": "<project-name>",
"author": "Bryan Aguilar",
"description": "<some-description>",
"private": true,
"version": "0.1.0",
"license": "MIT",
"type": "module",
"engines": {
"node": ">=18.12.1",
"yarn": ">=1.22.0",
"npm": "please-use-yarn"
},
...
}

Puedes verificar esta configuración intentando usar otra versión de node y otro gestor de paquetes.

Error cuando intentas usar npm
Error cuando usas otra versión de node

Herramientas de formato y calidad del código

Vamos a establecer un estándar que será utilizado por ti y tu equipo con el objetivo de mantener el estilo de código coherente y las mejores prácticas básicas. Vamos a usar dos herramientas muy conocidas:

  • ESLint: normas o reglas de codificación
  • Prettier: formateo automático

Vamos a instalar varias dependencias que nos ayudarán a crear estas reglas y estándares de codificación:

yarn add --dev eslint prettier @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-config-prettier eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react eslint-import-resolver-typescript eslint-plugin-react-hooks eslint-plugin-prettier

🚧 A tener en cuenta: Todas estas dependencias serán útiles durante el desarrollo, por eso usamos el flag — — dev en el comando anterior.

Ahora inicializamos nuestro archivo de configuración para ESLint con el siguiente comando:

npx eslint --init

Esta es la configuración que te recomiendo

En el punto, ¿Le gustaría definir un estilo para su proyecto?, te recomiendo no utilizar ninguno, ya que vamos a utilizar un estilo personalizado.

Cuando te pregunte sobre las reglas de formato de código como el tipo de indentación, el tipo de comillas o el punto y coma, puedes responder cualquiera, más adelante modificaremos estas reglas.

Cuando instalemos las dependencias adicionales que requerimos y el proceso haya finalizado, obtendremos como resultado el archivo eslintrc.cjs, más adelante vamos a hacer unos cambios en este archivo

Para configurar prettier, creados tres archivos en la raíz de la aplicación:

.prettierrc

{
"arrowParens": "always",
"bracketSpacing": true,
"insertPragma": false,
"printWidth": 120,
"proseWrap": "preserve",
"quoteProps": "as-needed",
"requirePragma": false,
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "none",
"useTabs": true,
"endOfLine": "auto",
"overrides": [
{
"files": ".prettierrc",
"options": { "parser": "typescript" }
}
]
}

.prettierignore

.yarn
dist
node_modules
.prettierrc

.eslintignore

node_modules/
dist/

🚧 A tener en cuenta: Todas estas configuraciones son las que uso en mis proyectos, puedes cambiar estos valores para ajustar las necesidades a tu equipo o a tu gusto.

Ahora, vamos a cambiar las configuraciones en nuestro archivo eslintrc.cjs

module.exports = {
env: {
browser: true,
es2021: true,
jest: true
},
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:import/recommended',
'plugin:import/typescript',
'plugin:jsx-a11y/recommended',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
'eslint-config-prettier',
'prettier'
],
settings: {
react: {
version: 'detect'
},
'import/parsers': {
'@typescript-eslint/parser': ['.ts', '.tsx']
}
},
overrides: [],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
jsx: true
},
ecmaVersion: 'latest',
sourceType: 'module'
},
plugins: ['react', 'react-hooks', 'prettier', '@typescript-eslint', 'import'],
rules: {
'import/no-unresolved': 'error',
'react/react-in-jsx-scope': 'off',
camelcase: 'error',
'spaced-comment': 'error',
quotes: ['error', 'single'],
'no-duplicate-imports': 'error',
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/explicit-function-return-type': 'error'
}
};

Ahora añadimos un nuevo script a package.json para poder ejecutar nuestros formateadores de código:

"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"lint": "eslint src/**/*.{js,jsx,ts,tsx,json}",
"lint:fix": "eslint --fix src/**/*.{js,jsx,ts,tsx,json}",
"format": "prettier --write src/**/*.{js,jsx,ts,tsx,scss,md,json} --config ./.prettierrc"
},

Husky

Es una herramienta para ejecutar scripts en diferentes etapas del proceso git, por ejemplo add, commit, push, etc. Nos gustaría poder establecer condiciones, y solo permitir cosas como commit y push para tener éxito si nuestro código cumple con esas condiciones.

🚧 A tener en cuenta: Puedes implementar comandos como ejecutar pruebas, generar el build de producción o muchas cosas más con esta herramienta, en mi caso la usaré para formatear el código antes de hacer un commit.

Para instalar Husky

yarn add -D husky
npx husky install

El segundo comando creará un directorio .husky en tu proyecto. Aquí es donde vivirán tus hooks. Asegúrate de agregar un nuevo script a package.json:

"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"lint": "eslint src/**/*.{js,jsx,ts,tsx,json}",
"lint:fix": "eslint --fix src/**/*.{js,jsx,ts,tsx,json}",
"format": "prettier --write src/**/*.{js,jsx,ts,tsx,scss,md,json} --config ./.prettierrc",
"prepare": "husky install"
},

Esto garantizará que Husky se instale automáticamente cuando otros desarrolladores ejecuten el proyecto. Dentro del directorio .huskyexiste un archivo llamado pre-commit, dentro agrega lo siguiente:

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

# This setting causes the formatting to be applied to all files in your project.
yarn format && yarn lint:fix && git add .

🚧 A tener en cuenta: Lo anterior dice que para que nuestro commit tenga éxito, los scripts yarn format, yarn lint:fix y git add .debe ejecutarse primero y tener éxito.

También puedes un hook usando el siguiente comando

npx husky add .husky/pre-push "yarn format"

🚧 A tener en cuenta: Lo anterior asegura que no se nos permite hacer push al repositorio remoto a menos que nuestro código pueda construirse con éxito.

Estándar en commits

Vamos a seguir una convención estándar para todos nuestros mensajes de commit, vamos a asegurarnos de que todo el equipo también siga este estándar (¡incluidos nosotros mismos!). Podemos añadir un linter para nuestros mensajes de confirmación:

yarn add -D @commitlint/config-conventional @commitlint/cli

Para configurarlo usaremos un conjunto de valores predeterminados estándar, incluiremos esta lista en el archivo commitlint.config.cjs:

// build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
// ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
// docs: Documentation only changes
// feat: A new feature
// fix: A bug fix
// perf: A code change that improves performance
// refactor: A code change that neither fixes a bug nor adds a feature
// style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
// test: Adding missing tests or correcting existing tests

module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'body-leading-blank': [1, 'always'],
'body-max-line-length': [2, 'always', 100],
'footer-leading-blank': [1, 'always'],
'footer-max-line-length': [2, 'always', 100],
'header-max-length': [2, 'always', 100],
'scope-case': [2, 'always', 'lower-case'],
'subject-case': [2, 'never', ['sentence-case', 'start-case', 'pascal-case', 'upper-case']],
'subject-empty': [2, 'never'],
'subject-full-stop': [2, 'never', '.'],
'type-case': [2, 'always', 'lower-case'],
'type-empty': [2, 'never'],
'type-enum': [
2,
'always',
[
'build',
'chore',
'ci',
'docs',
'feat',
'fix',
'perf',
'refactor',
'revert',
'style',
'test',
'translation',
'security',
'changeset'
]
]
}
};

A continuación, vamos a activa este estándar con Husky usando:

npx husky add .husky/commit-msg 'npx --no -- commitlint --edit "$1"'

A veces el comando anterior no funciona en algunos intérpretes de comandos. Puede probar otros comandos como:

npx husky add .husky/commit-msg \"npx --no -- commitlint --edit '$1'\"

or

npx husky add .husky/commit-msg "npx --no -- commitlint --edit $1"

Ten en cuenta que los mensajes de commits que hemos configurado tienen este formato

git commit -m "type: message"

Donde type puede ser alguno de estos valores:

// build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
// ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
// docs: Documentation only changes
// feat: A new feature
// fix: A bug fix
// perf: A code change that improves performance
// refactor: A code change that neither fixes a bug nor adds a feature
// style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
// test: Adding missing tests or correcting existing tests

Ahora vamos a hacer una prueba usando un mensaje para un commit con un tipo que no se encuentra en nuestra configuración de commitlint y sin un mensaje:

Como podemos ver, al ejecutar un commit, se verifica que el formato de nuestro código sea el adecuado de acuerdo a nuestras reglas de ESLint y Prettier, además se verifica que el mensaje para nuestro commit sea el adecuado.

Ten en cuenta que a partir de este punto utilizaremos el estándar Conventional Commits y la convención de Angular descrita aquí.

Si trabajas en un equipo de desarrolladores es importante acordar y seguir un estándar, personalmente me gusta mucho seguir el estándar que te he mostrado anteriormente.

Configuración de VS Code

Ahora que hemos implementado ESLint y Prettier podemos aprovechar algunas funcionalidades convenientes de VS Code para que se ejecuten automáticamente.

También puedes crear un directorio en la raíz de tu proyecto llamado .vscodey dentro un archivo llamado settigns.json. Esto será una lista de valores que van a sobreescribir la configuración por defecto de tu VS Code. La razón de esto, es que podemos establecer ajustes específicos para el proyecto.

.vscode/settings.json

{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll": true,
"source.organizeImports": true
}
}

🚧 A tener en cuenta: Puedes agregar tantas configuraciones como desees, o también puedes agregar estos valores en la configuración de VS Code, en mi caso, las configuraciones de mi editor son:

"editor.formatOnSave": true,
"prettier.requireConfig": true,
"typescript.preferences.importModuleSpecifier": "relative",
"editor.codeActionsOnSave": {
"source.organizeImports": true
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[jsonc]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[scss]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"prettier.printWidth": 140

Estructura de directorios

En mis proyectos, intento seguir la siguiente estructura de directorios

  • media directory Este directorio puede almacenar archivos de apoyo para la aplicación. Cosas como documentación, requisitos, esquemas, imágenes de la aplciación, etc.
  • assets directory — Este directorio contiene los recursos asociados a la plicación, aquí se encuentran las imágenes, hojas de estilo, íconos, tipografías y demás recursos de terceros.
  • components directory — Este directorio es muy importante, aquí se almacenan los componentes que se utilizarán para crear las vistas de la aplicación.
  • data directory — Este directorio contiene todo lo relacionado con los datos que van a alimentar y dar funcionalidad a la aplicación.
  • hooks directory — Este directorio se utiliza para almacenar hooks que permiten el manejo del estado de la aplicación. También se encuentran los hooks personalizados (es importante mencionar que cuando se crea un hook personalizado el nombre de este inicia con el prefijo, use por ejemplo useFetch).
  • routes directory — Almacén de rutas de la aplicación (genéricas, públicas o privadas).
  • utils directory — Este directorio contiene funciones que se utilizan a lo largo del proyecto.
  • pages directory — Este directorio contiene las páginas construidas con los componentes, (si las vistas tienen demasiados componentes es necesario crear un directorio de components).
Estructura de directorios

Uso de SCSS

En mis proyectos acostumbro a usar SCSS para las hojas de estilo del proyecto, para ello, basta con instalar la siguiente dependencia de desarrollo:

yarn add -D sass

Posteriormente, cambia las extensiones de tus archivos .css por .scss

Usando SCSS

Configuración de alias para rutas en tu proyecto

Es importante mantener las importaciones lo más claras, sobre todo si tu proyecto crece con el tiempo, para ello, una buena práctica es crear alias para los paths en nuestro proyecto. Se hace esta configuración para evitar importaciones de este tipo:

../../../<file-name>

En Vite, esta configuración es sencilla, para ello, modificamos nuestro archivo vite.config.ts:

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc';
import * as path from 'path';

// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@/': path.resolve(__dirname, './src'),
'@/config': path.resolve(__dirname, './src/config'),
'@/components': path.resolve(__dirname, './src/components'),
'@/styles': path.resolve(__dirname, './src/styles'),
'@/utils': path.resolve(__dirname, './src/utils'),
'@/common': path.resolve(__dirname, './src/common'),
'@/assets': path.resolve(__dirname, './src/assets'),
'@/pages': path.resolve(__dirname, './src/pages'),
'@/routes': path.resolve(__dirname, './src/routes'),
'@/layouts': path.resolve(__dirname, './src/layouts'),
'@/hooks': path.resolve(__dirname, './src/hooks'),
'@/store': path.resolve(__dirname, './src/store')
}
}
});

🚧 A tener en cuenta: Puede que tengas un error en la importación de path para solucionarlo, puedes instalar los tipos de node:

yarn add -D @types/node

Ahora, vamos a agregar una configuración en el archivo tsconfig.json denominada paths :

{
"compilerOptions": {
...
"noEmit": true,
"jsx": "react-jsx",
"paths": {
"@/*": ["./src/*"],
"@/components/*": ["./src/components/*"],
"@/styles/*": ["./src/styles/*"],
"@/config/*": ["./src/config/*"],
"@/utils/*": ["./src/utils/*"],
"@/common/*": ["./src/common/*"],
"@/assets/*": ["./src/assets/*"],
"@/pages/*": ["./src/pages/*"],
"@/routes/*": ["./src/routes/*"],
"@/hooks/*": ["./src/hooks/*"],
"@/store/*": ["./src/store/*"]
},
"typeRoots": ["./typings/"]
},
"include": ["src", ".eslintrc.cjs", "./vite.config.ts"],
"exclude": ["node_modules"],
"references": [{ "path": "./tsconfig.node.json" }]
}

Configurando ambiente de Testing

Para configurar nuestro ambiente de pruebas, vamos a hacer uso de Jest y React Testing Library. Instalamos las siguientes dependencias:

yarn add --dev jest @types/jest babel-jest @babel/core @babel/preset-env isomorphic-fetch @testing-library/react jest-environment-jsdom @babel/preset-react @babel/preset-typescript

Con todas estas dependencias instaladas, creamos dos nuevos comandos en nuestro package.json:

"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"lint": "eslint src/**/*.{js,jsx,ts,tsx,json}",
"lint:fix": "eslint --fix src/**/*.{js,jsx,ts,tsx,json}",
"format": "prettier --write src/**/*.{js,jsx,ts,tsx,scss,md,json} --config ./.prettierrc",
"prepare": "husky install",
"test": "jest --watchAll",
"test:coverage": "jest --coverage"
},

Ahora vamos a crear un par de archivos de configuración en la raíz de nuestra aplicación:

babel.config.cjs

module.exports = {
presets: [
['@babel/preset-env', { targets: { esmodules: true, node: 'current' } }],
['@babel/preset-react', { runtime: 'automatic' }],
'@babel/preset-typescript'
]
};

jest.config.js

export default {
testEnvironment: 'jest-environment-jsdom',
setupFiles: ['./jest.setup.js']
};

jest.setup.js

import 'isomorphic-fetch'

🚧 A tener en cuenta: Este último archivo de configuración permite usar el método fetchen tus pruebas. Si estás usando node ≥ 18, NO necesitas esta configuración.

Ahora puedes crear un componente para verificar el correcto funcionamiento de jest:

// src/Component.tsx

export const Component = (): JSX.Element => {
return <h1>Vite</h1>;
};

Y vamos a crear su respectivo archivo de prueba:

// __test__/Component.test.tsx

import { render, screen } from '@testing-library/react';
import React from 'react';
import { Component } from '../src/Component';

describe('Tests on <App/> component', (): void => {
test('renders Vite + React h1', (): void => {
render(<Component />);
const h1Element = screen.getByText(/Vite/i);
expect(h1Element).toBeTruthy();
});
});

Cuando ejecutamos el comando, yarn testel resultado es el siguiente:

Otras consideraciones

Uso de Git

Se recomienda el uso de Git para los proyectos (que dependan de trabajo colaborativo), inicialmente se recomienda crear tres ramas:

  1. master/main: código completamente funcional o producción
  2. staging: funciona como entorno de pruebas. Una vez se pase de develop a esta rama, únicamente se harán pruebas
  3. develop: todo el desarrollo se hace aquí, los distintos desarrolladores sacan sus cambios desde esta rama.
git checkout -b staging
git checkout -b develop

Debe estar posicionado en develop para iniciar el desarrollo.

Variables de entorno

Puede ser que necesites usar variables de entorno en alguna etapa del desarrollo, este concepto es importante ya que te permite abstraer variables que resulten de importancia a la hora de mantener y producir tu proyecto.

Atomic design

Otro aspecto a tomar en cuenta es unar la metodología de atomic design para diseñar, crear y organizar tus componentes. Esta metodología provee una idea de organización muy peculiar, al organizar tus componentes en átomos, moléculas, organismos, templates o plantillas.

Lazy Loading

La carga diferida es un patrón de diseño comúnmente usado en la programación informática que consiste en retrasar la carga o inicialización de un objeto hasta el mismo momento de su utilización. Esto contribuye a la eficiencia de los programas, evitando la precarga de objetos que podrían no llegar a utilizarse.

Storybook

Storybook es un taller de frontend para construir componentes y páginas de interfaz de usuario de forma aislada. Miles de equipos lo utilizan para desarrollar, probar y documentar la interfaz de usuario.

Puede que resulte mucho trabajo, implementar todo esto ANTES de iniciar con tu desarrollo, sin embargo considero que es algo esencial en tus proyectos, las buenas practicas, un estándar de trabajo y una predisposición a hacer las cosas de la mejor manera te ayudarán a crecer como desarrollador y trabajar de una manera más ordenada y proactiva. Ahora te invito a que sigas estos consejos y recomendaciones para tus desarrollos actuales y futuros.

Código fuente de este proyecto

Puedes encontrar este y otros en mi repositorio de Github . No olvides visitar mi página web.

Gracias por leer este artículo!

Si deseas hacerme alguna pregunta, no lo dudes!. Mi bandeja de entrada siempre estará abierta. Ya sea que tengas una pregunta o simplemente me quieras saludar, ¡haré todo lo posible para responderle!

--

--

Bryan Aguilar

Senior Software Engineer · Systems Engineer · Full Stack Developer · Enthusiastic Data Analyst & Data Scientist | https://www.bryan-aguilar.com