Accueil / Articles / Une base React prête pour la production : ce que fait réellement chaque package.

Une base React prête pour la production : ce que fait réellement chaque package.

Installer Vite, Tailwind v4, Redux Toolkit, React Router, Jest et Prettier pour une application React, ainsi que comprendre pourquoi chaque package et ligne de configuration est présent.

3165 mots

L’exécution de npm create vite vous fournit une application React qui s’affiche, mais ce n’est pas celle que l’on pourrait donner à des utilisateurs réels : il n’y a ni système de mise en forme, ni état partagé, ni routage, ni tests, et aucun format de code standardisé. Ce guide construit progressivement ces éléments manquants à l’aide de Tailwind CSS, Redux Toolkit, React Router, Jest avec React Testing Library et Prettier. Pour chaque package, il répond à deux questions : quel est son fonctionnement réel, et qu’est-ce qui ne fonctionne plus si on l’omet ? À la fin, vous disposerez d’une base fonctionnelle sur laquelle développer des fonctionnalités, et, tout aussi important, vous serez capable de lire votre propre package.json et d’expliquer chaque ligne.

Aperçu de la stack :

  • Tailwind CSS pour la mise en forme
  • Redux Toolkit pour les données partagées de l’application
  • React Router pour la navigation entre pages
  • Jest, React Testing Library ainsi qu’un petit ensemble d’outils Babel afin que les tests puissent s’exécuter
  • Prettier pour que le formatage ne dépende plus de l’opinion personnelle
  • React Testing Library est en réalité trois paquets ayant chacun des fonctions distinctes.

    Commencez par un projet Vite vierge en utilisant le modèle React + TypeScript :

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

    Tailwind CSS : le stylage est défini en premier

    Paquets : tailwindcss, @tailwindcss/vite

    npm install tailwindcss @tailwindcss/vite
    

    Cela installe les deux paquets en tant que dépendances régulières plutôt qu’en tant que devDependencies. Stricto sensu, aucun des paquets ne s’exécute dans le navigateur : le plugin Vite effectue son travail au moment de la compilation et seuls les CSS générés finissent dans le bundle de production. De nombreuses équipes les placent donc en tant que devDependencies, et pour une application single-page compilée, les deux choix produisent le même résultat. Choisissez une convention et restez cohérent.

    Ensuite, enregistrez le plugin aux côtés du plugin React dans la configuration Vite :

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

    Puis remplacez le contenu de src/index.css par une seule importation. Voici le contenu intégral du fichier :

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

    C’est vraiment tout pour la configuration. Tailwind v4 est basé sur le CSS en premier : il n’y a ni tailwind.config.js ni liste de globules de contenu, car il cherche automatiquement les noms de classes dans vos fichiers sources.

    Vérifier son fonctionnement

    Ajoutez temporairement quelques classes utilitaires à un en-tête dans App.tsx, par exemple text-3xl font-bold text-blue-600, exécutez npm run dev, et vérifiez que l’en-tête a changé. S’il change, le plugin et l’import CSS sont bien connectés.

    Pourquoi des classes utilitaires plutôt que des feuilles de style séparées

    Tailwind conserve les styles directement sur le markup qu’ils affectent. Avec un fichier CSS distinct, il est facile d’éditer un composant tout en oubliant sa feuille de style, ce qui entraîne progressivement l’accumulation de règles inutilisées ou obsolètes. Les tableaux de bord, en particulier, répètent souvent les mêmes éléments (cartes, badges, boutons) ; en les composant à partir d’un ensemble commun de classes utilitaires, on assure leur cohérence visuelle tout en nécessitant moins de code à maintenir. Le compromis réside dans un changement d’habitude : au lieu d’inventer des noms de classes tels que .card-header-active, on assemble chaque élément à partir de petites classes prédéfinies.

    Redux Toolkit : un store et un pont vers React

    Packages : @reduxjs/toolkit, react-redux

    Ces deux packages sont facilement confondus, mais ils remplissent des fonctions différentes :

    • @reduxjs/toolkit est le store lui-même : il stocke les données de l’application et y applique des mises à jour.
    • react-redux est la connexion vers React : il fournit <Provider> ainsi que les hooks que les composants utilisent pour lire et mettre à jour ces données.

    Vous avez besoin des deux, car aucun ne peut remplacer le rôle de l’autre.

    npm install @reduxjs/toolkit react-redux
    

    Créez le store dans src/app/store.ts. Il commence avec un tableau réducteur vide et exporte deux types dérivés du store afin que le reste de l’application n’ait jamais à les écrire manuellement :

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

    L’objet reducer: {} reste pour l’instant vide. Les slices seront ajoutés une fois que des fonctionnalités réelles, telles que des projets ou des tâches, en auront besoin ; il n’y a aucun intérêt à créer un état avant que n’importe quelle interface ne l’utilise.

    Ensuite, définissez des hooks typés dans src/app/hooks.ts. Les outils withTypes, disponibles dans les dernières versions de React Redux, lient useDispatch et useSelector aux types de votre store une seule fois, permettant ainsi aux composants d’obtenir une inférence de type complète sans avoir à annoter chaque appel :

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

    Fournir le store à l’arbre des composants

    À ce stade, le store existe, mais React n’en a aucune connaissance. <Provider> le rend disponible à tous les composants qui se trouvent en dessous de lui, c’est pourquoi il doit être placé tout en haut de l’arbre dans 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>,
    )
    

    Tout ce qui est affiché à l’intérieur de <Provider> peut désormais appeler useAppSelector et useAppDispatch.

    Vérifier que cela fonctionne

    Démarrez l’application et confirmez que la page s’affiche toujours sans l’erreur "could not find react-redux context". Cette erreur apparaît chaque fois qu’un composant utilise les hooks Redux en dehors d’un Provider. Avec un store vide, il n’y a rien d’autre à tester pour l’instant.

    Quand recourir à Redux et quand useState suffit

    Tout ne doit pas être stocké dans Redux, et placer tout l’état dans le store est une erreur tout autant que de garder tout en local. Une règle pratique :

    • useState pour les données pertinentes pour une seule page ou composant : par exemple, si un modal est ouvert, la valeur actuelle d’un champ de saisie, l’option sélectionnée dans un menu déroulant.
  • Redux pour les données nécessaires simultanément à plusieurs écrans ou composants, comme une liste de tâches affichée sur plusieurs pages, ou une seule tâche qui apparaît dans le tableau de bord, la liste des tâches et l’affichage détaillé d’une tâche.
  • Si un état devrait normalement être transmis à travers plusieurs niveaux ou dupliqué entre les écrans, c’est un bon signe qu’il doit être stocké dans le store.

    React Router : routage avant la première page réelle

    Paket : react-router

    Intégrer le routage avant même l’existence d’une page réelle peut sembler prématuré, mais cela en vaut la peine rapidement : chaque nouvel écran devient une <Route> supplémentaire, plutôt que de devoir restructurer l’application ultérieurement.

    npm install react-router
    

    Mettez la table des routes dans son propre module, src/routes/AppRoutes.tsx. Pour l’instant, elle associe / à un composant de remplacement stylisé avec les outils 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 affiche ensuite simplement cette table des routes :

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

    Finalement, enveloppez l’application dans <BrowserRouter> à l’intérieur de src/main.tsx, à côté du fournisseur 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 chaîne résultante est main.tsx<App /><AppRoutes /> → le composant <Route> correspondant à l’URL. Redux et le routeur sont indépendants, donc l’ordre de leur emboîtement n’a pas d’importance ; la seule exigence est que tous deux enveloppent <App>.

    Vérifier son fonctionnement

    Exécutez npm run dev et ouvrez /. Si le texte de remplacement apparaît, <BrowserRouter>, <Routes> et <Route> sont tous correctement connectés.

    Jest et React Testing Library : quatre tâches, onze paquets

    Paquets : jest, @testing-library/react, babel-jest ainsi que plusieurs autres

    C’est l’étape qui prend le plus de temps. Les concepts ne sont pas difficiles, mais « ajouter des tests » signifie en réalité installer une dizaine de paquets couvrant quatre responsabilités distinctes, puis faire en sorte qu’un seul test passe avec le routeur en place. Regrouper les paquets par tâche rend tout cela beaucoup plus facile à suivre.

    Groupe A : l’exécuteur de tests et un navigateur simulé

    npm install -D jest jest-environment-jsdom
    
    • jest est l’exécuteur. Il découvre les fichiers *.test.tsx, les exécute et indique les tests réussis ou échoués. Rien d’autre dans cette section ne fonctionne sans lui.
    • jest-environment-jsdom est nécessaire car Jest s’exécute sous Node, où il n’y a pas de document. Il fournit un DOM simulé afin que les composants aient un endroit où s’afficher.

    Groupe B : React Testing Library se compose de trois paquets

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

    Ce que les gens appellent "React Testing Library" est en réalité trois bibliothèques, chacune ayant sa propre fonction :

    • @testing-library/react affiche un composant sur la page simulée et fournit des méthodes de recherche telles que screen.getByText(...).
  • @testing-library/jest-dom ajoute des comparateurs lisible tels que toBeInTheDocument(), ce qui vous évite de comparer manuellement les résultats des requêtes à null.
  • @testing-library/user-event simule un comportement d’utilisateur réaliste. La saisie génère l’ensemble de la séquence de mise en surbrillance, de pression de touche, d’entrée et de relâchement de touche que le navigateur enverrait, plutôt qu’un seul événement synthétique envoyé à un élément.
  • En bref : afficher, vérifier, interagir. Trois tâches, trois packages, et vous en avez presque toujours besoin tous.

    Groupe C : la chaîne d’outils Babel qui permet à Jest de lire le TSX

    Ce groupe existe pour une seule raison : Jest ne peut pas comprendre seul les fichiers TypeScript ou JSX.

    • babel-jest relie les deux. Jest fait passer chaque fichier par Babel avant de l’exécuter.
    • @babel/preset-typescript supprime les annotations de type. Il ne vérifie pas le type de quoi que ce soit ; il se contente d’enlever les syntaxes : string et similaires.
    • @babel/preset-react compile JSX en appels de fonction ordinaires.
    • @babel/preset-env transforme la syntaxe moderne en quelque chose que votre version de Node prend en charge.

    Contrairement au Groupe B, installez-les tous ensemble en une seule commande. Ces présets exigent tous un @babel/core compatible, et les installer séparément dans un projet qui contient déjà Jest (qui intègre ses propres dépendances Babel) peut amener npm à essayer de concilier des versions incompatibles. Un symptôme courant est une erreur ERESOLVE unable to resolve dependency tree lors de la prochaine installation d’un seul package. Installer l’ensemble du groupe en une seule fois permet à npm de résoudre un ensemble cohérent.

    Un deuxième piège, plus subtil, provient de la copie de commandes longues depuis des PDF ou des pages web. Le texte avec saut de ligne automatique peut se transformer en vrais sauts de ligne lorsqu’il est collé, ce qui fait que le nom d’un paquet comme @babel/preset-typescript est divisé en deux, et le shell exécute la seconde partie comme une commande séparée et sans sens. Les continuations de ligne explicites placent les sauts exactement là où vous le souhaitez. Ce qui suit utilise la syntaxe de l’invite de commandes Windows :

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

    Le ^ à la fin indique à cmd.exe que la commande se poursuit sur la ligne suivante. Dans PowerShell, le caractère de continuation est un backtick, tandis que dans bash ou zsh c’est un backslash. Quel que soit le shell, il s’agit toujours d’une seule commande npm install.

    Groupe D : types uniquement pour votre éditeur

    npm install -D @types/jest
    

    Ce package n’a aucun effet sur le déroulement des tests ; Babel a déjà supprimé tous les types à ce stade. Il existe afin que TypeScript et votre éditeur reconnaissent des variables globales comme test(...) et expect(...) au lieu de les signaler comme des erreurs.

    Ajout des scripts de test

    L’installation de Jest ne vous fournit pas de commande npm test ; vous devez donc ajouter manuellement les scripts dans package.json:

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

    npm test exécute l’ensemble des tests une seule fois. npm run test:watch reste en exécution et ne réexécute que les tests affectés par le fichier que vous venez de sauvegarder ; gardez-le ouvert dans un deuxième terminal pendant que vous travaillez.

    Deux autres commandes méritent d’être mémorisées en cas de problème :

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

    La commande de cache est plus importante qu’il n’y paraît. Jest met en cache les fichiers transformés, de sorte que, après avoir modifié babel.config.cjs ou jest.config.cjs, il peut continuer à servir les anciennes versions et signaler une erreur que vous avez déjà résolue. Lorsqu’une correction ne semble pas fonctionner, videz le cache avant de conclure que la correction est erronée.

    La configuration complète du test

    Ci-dessous se trouvent toutes les fiches de configuration dans leur intégralité, accompagnées d’une explication de la fonction de chaque élément.

    babel.config.cjs

    Les préférences prédéfinies reproduisent celles du Groupe C : elles ciblent la version actuelle de Node, utilisent l’exécutable JSX automatique afin que les fichiers n’aient pas besoin d’importer React, et suppriment TypeScript. Le plugin intégré gère quelque chose que Jest ne peut pas faire : import.meta, que le code Vite utilise pour des éléments tels que import.meta.env et le remplacement en temps réel de modules, mais qui n’est pas valide dans le format CommonJS utilisé par Jest.

    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],
    }
    

    Ce est un véritable plugin Babel, écrit sous forme de fonction intégrée plutôt que de package installé ; Babel accepte les deux formes. MetaProperty est le type de nœud AST que Babel utilise pour import.meta, et le visiteur remplace chaque occurrence par un objet simple possédant une url vide et une valeur hot non définie. Notez que cela cache également toutes les valeurs de import.meta.env pour le code testé, ce qui signifie qu’un composant lisant des variables d’environnement devra les simuler séparément.

    jest.config.cjs

    Ce fichier relie le moteur d’exécution à tout le reste. Il sélectionne l’environnement jsdom, charge un fichier de configuration une fois l’environnement prêt, fait passer chaque fichier JavaScript et TypeScript par babel-jest, et mappe les imports de styles et d’images vers des modules de substitution.

    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', }, }

    L’entrée transformIgnorePatterns mérite une remarque. Par défaut, Jest ne transforme rien à l’intérieur de node_modules. La recherche en avant négative fait une exception pour react-router et cookie-es, qui sont publiés sous forme de modules ES que le pipeline CommonJS de Jest ne peut pas charger sans transformation. Si vous ajoutez plus tard une autre dépendance réservée aux ESM et que vous obtenez un SyntaxError: Cannot use import statement outside a module, c’est ici que ce motif doit être placé.

    jest.setup.ts

    Le fichier de configuration enregistre les matchers jest-dom et ajoute TextEncoder ainsi que TextDecoder au scope global. L’environnement jsdom ne les expose pas, tandis que React Router exige qu’ils existent ; ils sont donc empruntés au module node:util de Node :

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

    Mocking des styles et des fichiers

    Jest ne sait pas comment importer un fichier CSS ou un PNG. Les deux modules de base ci-dessous sont ceux vers lesquels moduleNameMapper redirige ces importations, de sorte qu’un composant contenant import './App.css' ne fait pas planter l’exécution du test :

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

    src/App.test.tsx

    Finalement, le test qui met à l’épreuve toute la configuration. Il affiche App à l’intérieur d’un MemoryRouter, qui conserve l’état de routage en mémoire au lieu de lire l’URL réelle du navigateur, et vérifie que le texte de remplacement est présent :

    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()
    })
    

    Même s’il est court, ce test touche à chaque élément clé : la compilation de TSX, le package de routage ESM, les polyfills d’encodage de texte, la gestion de import.meta ainsi que le comparateur jest-dom. S’il passe, la configuration est correcte.

    Prettier : mettre fin aux débats sur le formatage

    Pakets : prettier, eslint-config-prettier

    npm install -D prettier eslint-config-prettier
    
    • prettier reformate votre code selon un style unique et cohérent, généralement lors du enregistrement.
    • eslint-config-prettier a un but précis : il désactive les règles d’ESLint qui entrent en conflit avec le formatage de Prettier, comme les règles concernant les guillemets, les points-virgules et les virgules en fin de phrase.

    Le deuxième package ne définit aucune règle propre ; il sert uniquement à éviter que les deux outils ne se contredisent. Dans eslint.config.js, il doit être la dernière entrée de votre liste de configuration, car les entrées ultérieures prennent le pas sur celles précédentes, et il doit donc surcharger les autres plutôt que d’être lui-même surchargé par eux.

    Pourquoi il vaut la peine d’ajouter ce package supplémentaire

    En l’absence de mise en forme automatique, le temps consacré à la révision penche vers l’utilisation de tabulations plutôt que d’espaces, au détriment de la logique. Prettier propose délibérément seulement un petit nombre d’options, ce qui réduit les sujets de discussion, et c’est précisément l’objectif.

    Assembler tout cela

    Il reste un point à régler. tsc -b vérifie le type de tout ce qui se trouve sous src, y compris les fichiers de test, mais par défaut il ne connaît rien concernant test ou expect. Ajoutez les packages de types pertinents à l’array types dans tsconfig.app.json:

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

    Puis exécutez la séquence complète de vérification, en commençant par l’étape la moins coûteuse :

    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
    

    Le linting est rapide et permet de détecter les erreurs évidentes, les tests valident le comportement du logiciel, la phase de compilation effectue une véritable compilation pour générer le fichier Tailwind final, tandis que le serveur de développement, étant la vérification la plus lente, est le seul à prouver que l’application s’affiche correctement dans un navigateur. Lorsque ces quatre étapes réussissent, les bases sont posées, sans qu’aucune ligne de code fonctionnel n’ait été écrite.

    Une remarque sur les alternatives

    import.meta, les exceptions ESM) existe parce que Jest ne partage pas le pipeline de compilation de Vite. Si vous préférez moins d’éléments complexes, Vitest réutilise votre configuration Vite et fonctionne avec les mêmes paquets de Testing Library. Pour une approche différente visant à réduire les outils de test, consultez notre guide sur la substitution de Jest par le lanceur de tests natif de Node. La configuration Jest présentée ci-dessus reste un choix solide lorsque votre équipe connaît déjà Jest ou dépend de son écosystème.

    Points clés

    • Considérez chaque dépendance comme une décision : sachez ce qu’elle fait et ce qui échoue sans elle.
    • Tailwind v4 n’a besoin que du plugin Vite et d’une seule importation CSS ; aucun fichier de configuration n’est requis.
  • Redux Toolkit gère les données et React Redux les relie aux composants ; les hooks typés permettent une utilisation plus propre, tandis que l’état de l’interface utilisateur locale reste à gérer avec useState.
  • Intégrer le routeur dès le début permet de modifier chaque page future en une seule ligne.
  • Jest dans un projet Vite nécessite un exécuteur, un environnement DOM, trois paquets de la Testing Library, une chaîne d’outils Babel ainsi que quelques corrections de configuration ciblées ; si une correction semble ignorée, videz le cache de Jest.
  • Installez les paquets interdépendants en une seule commande, et utilisez des continuations de ligne explicites lorsque la commande s’étend sur plusieurs lignes.
  • Lectures complémentaires