Inicio / Artículos / Una línea de base de React lista para producción: qué hace realmente cada paquete

Una línea de base de React lista para producción: qué hace realmente cada paquete

Configura Vite, Tailwind v4, Redux Toolkit, React Router, Jest y Prettier para una aplicación React, y comprende por qué cada paquete y línea de configuración está presente.

3165 palabras

Ejecutar npm create vite te proporciona una aplicación React que se muestra, pero no es la que podrías entregar a usuarios reales: no cuenta con sistema de estilos, estado compartido, enrutamiento, pruebas ni un formato de código acordado. Esta guía construye paso a paso esa base que falta utilizando Tailwind CSS, Redux Toolkit, React Router, Jest con React Testing Library y Prettier. Para cada paquete, responde a dos preguntas: ¿qué hace realmente? y, ¿qué fallará si lo omitimos? Al final tendrás una base funcional sobre la cual desarrollar nuevas funciones y, lo que es igualmente importante, podrás leer tu propio package.json y explicar cada línea.

Un vistazo a la tecnología utilizada:

  • Tailwind CSS para el estilo
  • Redux Toolkit para los datos compartidos de la aplicación
  • React Router para la navegación entre páginas
  • Jest, React Testing Library y una pequeña cadena de herramientas de Babel para que las pruebas puedan ejecutarse
  • Prettier para que el formato deje de ser cuestión de opinión
  • Algunos de estos se instalan en una sola línea. Otros ocultan detalles sorprendentes; por ejemplo, “React Testing Library” en realidad son tres paquetes con funciones distintas.

    Comience con un proyecto Vite nuevo utilizando la plantilla React + TypeScript:

    npm create vite@latest react-production-stack -- --template react-ts
    cd react-production-stack
    npm install
    

    Tailwind CSS: el estilo se define primero

    Paquetes: tailwindcss, @tailwindcss/vite

    Dado que el estilo afecta a cada componente, tiene sentido verificar que funcione antes de agregar cualquier otra cosa.

    npm install tailwindcss @tailwindcss/vite
    

    Esto instala ambos paquetes como dependencias normales en lugar de devDependencies. Estrictamente hablando, ninguno de los paquetes se ejecuta en el navegador: el plugin de Vite realiza su trabajo en el momento de la compilación y solo el CSS generado termina en el paquete de producción. Por eso, muchos equipos los incluyen como devDependencies, y para una aplicación de página única empaquetada, cualquiera de las opciones genera el mismo resultado. Elige una convención y sé consistente con ella.

    A continuación, registra el plugin junto con el plugin de React en la configuración de Vite:

    import { defineConfig } from 'vite'
    import react from '@vitejs/plugin-react'
    import tailwindcss from '@tailwindcss/vite'
    
    export default defineConfig({
      plugins: [react(), tailwindcss()],
    })
    

    Luego reemplaza el contenido de src/index.css con una sola importación. Este es todo el contenido del archivo:

    /* Tailwind v4 is CSS-first. No config file, no content globs. */
    @import 'tailwindcss';
    

    Eso realmente es todo lo necesario para la configuración. Tailwind v4 sigue un enfoque basado en CSS: no existe un archivo tailwind.config.js ni una lista de globos de contenido, ya que escanea automáticamente los archivos fuente en busca de nombres de clases.

    Verificación del funcionamiento

    Agregue temporalmente algunas clases utilitarias a un encabezado en App.tsx, por ejemplo text-3xl font-bold text-blue-600, ejecute npm run dev y verifique que el encabezado cambie. Si lo hace, significa que el plugin y la importación de CSS están conectados.

    ¿Por qué utilidades en lugar de hojas de estilo separadas?

    Tailwind mantiene los estilos directamente en el marcado que afectan. Con un archivo CSS separado, es fácil editar un componente y olvidarse de su hoja de estilo, lo que hace que se acumulen gradualmente reglas obsoletas. En particular, los paneles de control repiten una y otra vez los mismos componentes básicos (tarjetas, insignias, botones), y componerlos a partir de un conjunto compartido de utilidades mantiene su consistencia visual con menos código por mantener. El sacrificio es un cambio de hábito: en lugar de inventar nombres de clases como .card-header-active, se compone cada elemento a partir de clases pequeñas y predefinidas.

    Redux Toolkit: un almacén y un puente hacia React

    Paquetes: @reduxjs/toolkit, react-redux

    Estos dos paquetes son fáciles de confundir, pero realizan funciones diferentes:

    • @reduxjs/toolkit es el almacén en sí: alberga los datos de la aplicación y aplica las actualizaciones a ellos.
    • react-redux es la conexión con React: proporciona <Provider> y los hooks que utilizan los componentes para leer y actualizar esos datos.

    Necesitas ambos, ya que ninguno puede realizar el trabajo del otro.

    npm install @reduxjs/toolkit react-redux
    

    Crea el almacén en src/app/store.ts. Comienza con un mapa de reducidores vacío y exporta dos tipos derivados del almacén para que el resto de la aplicación no tenga que escribirlos a mano:

    import { configureStore } from '@reduxjs/toolkit'
    
    export const store = configureStore({
      reducer: {},
    })
    
    export type RootState = ReturnType<typeof store.getState>
    export type AppDispatch = typeof store.dispatch
    

    Por ahora, el objeto reducer: {} permanece vacío. Las secciones se añadirán cuando se necesiten funcionalidades reales, como proyectos o tareas; no tiene sentido crear estado antes de que cualquier pantalla lo utilice.

    Luego se definen ganchos tipados en src/app/hooks.ts. Las herramientas withTypes, disponibles en las versiones recientes de React Redux, vinculan useDispatch y useSelector a los tipos de tu almacén una sola vez, de modo que los componentes obtienen inferencia de tipos completa sin necesidad de anotar cada llamada:

    import { useDispatch, useSelector } from 'react-redux'
    import type { AppDispatch, RootState } from './store'
    
    export const useAppDispatch = useDispatch.withTypes<AppDispatch>()
    export const useAppSelector = useSelector.withTypes<RootState>()
    

    Proporcionar el almacén al árbol de componentes

    En este punto el almacén ya existe, pero React no tiene conocimiento de él. <Provider> lo hace disponible para cada componente que se encuentre debajo de él, por lo que debe colocarse en la parte superior del árbol en src/main.tsx:

    import { StrictMode } from 'react'
    import { createRoot } from 'react-dom/client'
    import { Provider } from 'react-redux'
    import { store } from './app/store'
    import App from './App'
    import './index.css'
    
    createRoot(document.getElementById('root')!).render(
      <StrictMode>
        <Provider store={store}>
          <App />
        </Provider>
      </StrictMode>,
    )
    

    Cualquier elemento renderizado dentro de <Provider> ahora puede llamar a useAppSelector y useAppDispatch.

    Verificación de funcionamiento

    Inicie la aplicación y confirme que la página sigue renderizándose sin el error "no se pudo encontrar el contexto react-redux". Ese error aparece siempre que un componente utiliza los hooks de Redux fuera de un Provider. Con una tienda vacía aún no hay nada más que probar.

    Cuándo usar Redux y cuándo basta con useState

    No todo pertenece a Redux, y almacenar todo el estado en la tienda es tan erróneo como mantenerlo todo localmente. Una regla práctica:

    • useState para datos relevantes para una sola pantalla o componente: si un modal está abierto, el valor actual de un campo de entrada, la opción seleccionada en un menú desplegable.
  • Redux para los datos que varias pantallas o componentes necesitan al mismo tiempo, como una lista de tareas mostrada en múltiples páginas, o una tarea individual que aparece en el panel de control, la lista de tareas y la vista de detalles de la tarea.
  • Si algún estado de otro modo tendría que transmitirse a través de varias capas o duplicarse entre pantallas, eso es una buena señal de que pertenece al almacén de estado.

    React Router: enrutamiento antes de la primera página real

    Paquete: react-router

    Agregar enrutamiento antes de que exista cualquier página real puede parecer prematuro, pero da buenos resultados rápidamente: cada nueva pantalla se convierte en un <Route> adicional, en lugar de requerir una modificación posterior que obligue a reestructurar la aplicación.

    npm install react-router
    

    Coloque la tabla de rutas en su propio módulo, src/routes/AppRoutes.tsx. Por ahora, asigna / a un componente de marcador temporal estilizado con las herramientas de Tailwind:

    import { Route, Routes } from 'react-router'
    
    function Placeholder() {
      return (
        <div className="flex min-h-screen items-center justify-center">
          <p className="text-slate-600">Routes coming soon</p>
        </div>
      )
    }
    
    export function AppRoutes() {
      return (
        <Routes>
          <Route path="/" element={<Placeholder />} />
        </Routes>
      )
    }
    

    src/App.tsx simplemente renderiza esa tabla de rutas:

    import { AppRoutes } from './routes/AppRoutes'
    
    function App() {
      return <AppRoutes />
    }
    
    export default App
    

    Finalmente, envuelva la aplicación en <BrowserRouter> dentro de src/main.tsx, junto al proveedor de Redux:

    import { StrictMode } from 'react'
    import { createRoot } from 'react-dom/client'
    import { Provider } from 'react-redux'
    import { BrowserRouter } from 'react-router'
    import { store } from './app/store'
    import App from './App'
    import './index.css'
    
    createRoot(document.getElementById('root')!).render(
      <StrictMode>
        <Provider store={store}>
          <BrowserRouter>
            <App />
          </BrowserRouter>
        </Provider>
      </StrictMode>,
    )
    

    La cadena resultante es main.tsx<App /><AppRoutes /> → el <Route> que coincida con la URL. Redux y el router son independientes, por lo que su orden de anidamiento no importa; el único requisito es que ambos envuelvan a <App>.

    Verificación de funcionamiento

    Ejecuta npm run dev y abre /. Si aparece el texto de marcador, <BrowserRouter>, <Routes> y <Route> están conectados correctamente.

    Jest y React Testing Library: cuatro tareas, once paquetes

    Paquetes: jest, @testing-library/react, babel-jest y varios más

    Este es el paso que lleva más tiempo. Los conceptos no son difíciles, pero “añadir pruebas” implica instalar unos once paquetes que cubren cuatro responsabilidades distintas, y luego hacer que pase una sola prueba con el router activo. Agrupar los paquetes por tarea hace que todo sea mucho más fácil de seguir.

    Grupo A: el ejecutor de pruebas y un navegador simulado

    npm install -D jest jest-environment-jsdom
    
    • jest es el ejecutor. Descubre los archivos *.test.tsx, los ejecuta y reporta las pruebas exitosas y fallidas. Nada más en esta sección funciona sin él.
    • jest-environment-jsdom es necesario porque Jest se ejecuta en Node, donde no existe document. Proporciona un DOM simulado para que los componentes tengan un lugar donde renderizarse.

    Grupo B: React Testing Library está formado por tres paquetes

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

    Lo que la gente llama “React Testing Library” en realidad son tres bibliotecas, cada una con su propia función:

    • @testing-library/react renderiza un componente en la página simulada y proporciona consultas como screen.getByText(...).
  • @testing-library/jest-dom añade comparadores fáciles de leer como toBeInTheDocument(), por lo que no es necesario comparar manualmente los resultados de las consultas con null.
  • @testing-library/user-event simula un comportamiento de usuario realista. Al escribir se genera toda la secuencia de enfoque total, tecla presionada, entrada y tecla soltada que un navegador emitiría, en lugar de un único evento sintético enviado al elemento.
  • En resumen: renderizar, verificar y interactuar. Tres tareas, tres paquetes, y casi siempre se desea tenerlos todos.

    Grupo C: la cadena de herramientas Babel que permite a Jest leer TSX

    Este grupo existe por una sola razón: Jest no puede comprender por sí mismo los archivos de TypeScript o JSX.

    • babel-jest conecta los dos. Jest pasa cada archivo por Babel antes de ejecutarlo.
    • @babel/preset-typescript elimina las anotaciones de tipo. No realiza ninguna verificación de tipos; simplemente quita : string y sintaxis similar.
    • @babel/preset-react compila JSX en llamadas a funciones comunes.
    • @babel/preset-env transforma la sintaxis moderna en lo que admite tu versión de Node.

    A diferencia del Grupo B, instala estos elementos juntos con una sola orden. Todos los presets requieren un @babel/core compatible, y instalarlos por separado en un proyecto que ya contiene Jest (que incluye sus propias dependencias de Babel) puede hacer que npm intente armonizar versiones incompatibles. Un síntoma común es el error ERESOLVE unable to resolve dependency tree al instalar un solo paquete posteriormente. Instalar todo el grupo de una vez permite a npm resolver un conjunto coherente.

    Otra trampa, más sutil, proviene de copiar comandos largos de PDF o páginas web. El texto con formato suelto puede convertirse en saltos de línea reales al pegarse, por lo que un nombre de paquete como @babel/preset-typescript se divide en dos, y la shell ejecuta la parte final como un comando separado e sin sentido. Las continuaciones de línea explícitas colocan los saltos exactamente donde se desea. A continuación se muestra la sintaxis de la Síntesis de Comandos de Windows:

    npm install -D babel-jest ^
      @babel/core ^
      @babel/preset-env ^
      @babel/preset-react ^
      @babel/preset-typescript
    

    El ^ al final indica a cmd.exe que el comando continúa en la línea siguiente. En PowerShell, el carácter de continuación es un backtick, y en bash o zsh es una barra invertida. Sea cual sea la shell, sigue siendo exactamente un npm install.

    Grupo D: tipos solo para tu editor

    npm install -D @types/jest
    

    Este paquete no afecta de ninguna manera a la ejecución de las pruebas; para entonces Babel ya ha eliminado todos los tipos. Su existencia sirve para que TypeScript y tu editor reconozcan funciones globales como test(...) y expect(...) en lugar de marcarlas como errores.

    Agregar los scripts de prueba

    Instalar Jest no te proporciona la orden npm test, por lo que debes agregar los scripts tú mismo en package.json:

    "scripts": {
      "dev": "vite",
      "build": "tsc -b && vite build",
      "lint": "eslint .",
      "test": "jest",
      "test:watch": "jest --watch"
    }
    

    npm test ejecuta toda la suite una sola vez. npm run test:watch permanece en ejecución y vuelve a ejecutar solo las pruebas afectadas por el archivo que acabas de guardar; mantén esta terminal abierta mientras trabajas.

    Hay dos comandos más que vale la pena recordar para cuando surjan problemas:

    npx jest src/App.test.tsx   # run one file only
    npx jest --clearCache       # when Jest keeps showing an error
                                # you already fixed
    

    La orden de caché es más importante de lo que parece. Jest almacena en caché los archivos transformados, por lo que después de modificar babel.config.cjs o jest.config.cjs, puede seguir sirviendo la salida antigua y reportar un error que ya se ha solucionado. Cuando una corrección parezca no funcionar, borre la caché antes de concluir que la solución es incorrecta.

    La configuración completa de pruebas

    A continuación se muestra cada archivo de configuración en su totalidad, con una explicación de qué es lo que se encarga cada parte.

    babel.config.cjs

    Los valores predefinidos imitan al Grupo C: apuntan a la versión actual de Node, utilizan el entorno de ejecución JSX automático para que los archivos no necesiten importar React, y eliminan TypeScript. El plugin integrado maneja algo que Jest no puede hacer: import.meta, que el código de Vite utiliza para cosas como import.meta.env y el reemplazo en caliente de módulos, pero que no es válido en la salida CommonJS que ejecuta Jest aquí.

    function stripImportMeta() {
      return {
        visitor: {
          MetaProperty(path) {
            path.replaceWithSourceString('({ url: "", hot: undefined })')
          },
        },
      }
    }
    
    module.exports = {
      presets: [
        ['@babel/preset-env', { targets: { node: 'current' } }],
        ['@babel/preset-react', { runtime: 'automatic' }],
        '@babel/preset-typescript',
      ],
      plugins: [stripImportMeta],
    }
    

    Se trata de un verdadero complemento de Babel, escrito como una función inline en lugar de un paquete instalado; Babel acepta ambas formas. MetaProperty es el tipo de nodo AST que utiliza Babel para import.meta, y el visitador reemplaza cada aparición por un objeto simple que cuenta con un url vacío y un hot indefinido. Tenga en cuenta que esto también oculta cualquier valor de import.meta.env del código sometido a pruebas, por lo que un componente que lea variables de entorno necesitará simularlas por separado.

    jest.config.cjs

    Este archivo conecta el ejecutor con todo lo demás. Selecciona el entorno jsdom, carga un archivo de configuración una vez que el entorno está listo, envía cada archivo de JavaScript y TypeScript a través de babel-jest, y mapea las importaciones de estilos e imágenes a módulos ficticios.

    module.exports = {
      testEnvironment: 'jsdom',
      setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
      moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'mjs', 'json'],
      transform: {
        '^.+\\.(ts|tsx|js|jsx|mjs)
    : 'babel-jest', }, transformIgnorePatterns: ['node_modules/(?!(react-router|cookie-es)/)'], moduleNameMapper: { '\\.(css|less|scss|sass)
    : '<rootDir>/test/styleMock.js', '\\.(png|jpg|jpeg|gif|svg|webp)
    : '<rootDir>/test/fileMock.js', }, }

    La entrada transformIgnorePatterns merece una mención. Por defecto, Jest no transforma nada dentro de node_modules. La expresión regular con mirada negativa hace una excepción para react-router y cookie-es, que se publican como módulos ES y cuyo pipeline de CommonJS de Jest no puede cargarlos sin transformarlos. Si más adelante agrega otra dependencia exclusiva para ESM y recibe un SyntaxError: Cannot use import statement outside a module, este patrón es el lugar adecuado para él.

    jest.setup.ts

    El archivo de configuración registra los comparadores de jest-dom y agrega TextEncoder y TextDecoder al ámbito global. El entorno jsdom no los expone, mientras que React Router espera que existan, por lo que se toman prestados del módulo node:util de Node:

    import { TextEncoder, TextDecoder } from 'node:util'
    import '@testing-library/jest-dom'
    
    Object.assign(globalThis, { TextEncoder, TextDecoder })
    

    Mock de estilos y archivos

    Jest no tiene idea de cómo importar un archivo CSS o un PNG. Los dos módulos de ejemplo a continuación son donde moduleNameMapper redirige esas importaciones, de modo que un componente que contenga import './App.css' no provoca que la prueba falle:

    // test/styleMock.js
    module.exports = {}
    
    // test/fileMock.js
    module.exports = 'test-file-stub'
    

    src/App.test.tsx

    Finalmente, la prueba que utiliza toda la configuración. Muestra App dentro de un MemoryRouter, el cual almacena el estado de enrutamiento en memoria en lugar de leer la URL real del navegador, y verifica que el texto de marcador esté presente:

    import { render, screen } from '@testing-library/react'
    import { MemoryRouter } from 'react-router'
    import App from './App'
    
    test('renders the placeholder route content', () => {
      render(
        <MemoryRouter>
          <App />
        </MemoryRouter>,
      )
      expect(screen.getByText(/routes coming soon/i)).toBeInTheDocument()
    })
    

    A pesar de ser breve, esta prueba abarca cada aspecto relacionado: la compilación de TSX, el paquete de enrutamiento ESM, los polyfills de codificación de texto, el manejo de import.meta y el comparador jest-dom. Si pasa, la configuración es correcta.

    Prettier: poniendo fin a las discusiones sobre formato

    Paquetes: prettier, eslint-config-prettier

    npm install -D prettier eslint-config-prettier
    
    • prettier formatea tu código según un estilo único y consistente, generalmente al guardarlo.
    • eslint-config-prettier tiene un propósito muy específico: desactiva las reglas de ESLint que entran en conflicto con el formato de Prettier, como las reglas relacionadas con comillas, puntos y coma y comas al final de las frases.

    El segundo paquete no agrega reglas propias; solo evita que las dos herramientas entren en conflicto. En eslint.config.js debe ser la última entrada de tu lista de configuración, ya que las entradas posteriores sobrescriben a las anteriores, y necesita sobrescribirlas en lugar de ser sobrescrito por ellas.

    Por qué vale la pena usar este paquete adicional

    Sin formato automático, el tiempo de revisión se inclina hacia el uso de tabulaciones en lugar de espacios, en contra de la lógica adecuada. Prettier ofrece deliberadamente solo un puñado de opciones, por lo que queda poco sobre lo que discutir, y ese es precisamente el objetivo.

    Integrarlo todo

    Aún queda un vacío. tsc -b verifica el tipo de todo lo que se encuentra bajo src, incluidos los archivos de prueba, y por defecto no sabe nada sobre test ni expect. Añada los paquetes de tipos relevantes al array types en tsconfig.app.json:

    "types": ["vite/client", "jest", "@testing-library/jest-dom"]
    

    Luego ejecute la secuencia completa de verificación, comenzando por el paso más económico:

    npm run lint    # fast, catches obvious mistakes
    npm test        # fast, catches broken behaviour
    npm run build   # slower — real compile, real Tailwind output
    npm run dev     # slowest — but the only one that proves it renders
    

    El análisis con linter es rápido y detecta errores obvios; las pruebas confirman el comportamiento del código, la compilación realiza una verdadera compilación y genera el resultado final de Tailwind, mientras que el servidor de desarrollo, la verificación más lenta, es el único que demuestra que la aplicación se renderiza en un navegador. Cuando las cuatro pruebas tienen éxito, la base del proyecto está lista, sin ni una sola línea de código funcional.

    Una nota sobre alternativas

    Gran parte de la sección sobre Jest (los presets de Babel, el plugin import.meta, las excepciones ESM) existe porque Jest no comparte la pipeline de compilación de Vite. Si prefiere menos componentes interactivos, Vitest reutiliza su configuración de Vite y funciona con los mismos paquetes de Testing Library. Para conocer otro enfoque sobre cómo reducir las herramientas de pruebas, consulte nuestra guía sobre sustituir Jest por el ejecutor de pruebas nativo de Node. La configuración de Jest mencionada anteriormente sigue siendo una opción sólida cuando su equipo ya conoce Jest o depende de su ecosistema.

    Puntos clave

    • Trate cada dependencia como una decisión: sepa qué hace y qué falla sin ella.
    • Tailwind v4 solo necesita el plugin de Vite y una importación de CSS; no se requiere ningún archivo de configuración.
  • Redux Toolkit almacena los datos y React Redux los conecta a los componentes; los hooks tipados mantienen un uso limpio, y el estado de la interfaz local sigue estando en useState.
  • Agregar el router desde el principio hace que cada página futura requiera solo un cambio de una línea.
  • Jest en un proyecto Vite necesita un ejecutor, un entorno DOM, tres paquetes de Testing Library, una cadena de herramientas Babel y algunas correcciones específicas en la configuración; cuando alguna corrección parezca no aplicarse, borre la caché de Jest.
  • Instale los paquetes interdependientes con una sola orden, y utilice continuaciones explícitas de línea cuando una orden abarque varias líneas.
  • Lecturas relacionadas