Comment Créer Un Template Pour CRA ⚛️
14 avril 2020 | 9 mins de lecturePublié sur dev.to et medium.com
Récemment, j'ai pu bénéficier de beaucoup plus de temps libre qu'auparavant. La propagation de l'épidémie de COVID dans le monde (dans mon cas, en France) a mis en avant le télétravail (plus que d'habitude) ; et qui dit "travailler à distance" dit plus de temps pour soi. En effet, en supprimant la contrainte du trajet quotidien pour se rendre sur son lieu de travail, on peut gagner un temps considérable pour profiter de sa famille, jouer à des jeux vidéos (et / ou lire des livres), faire du sport (juste ce qu'il faut) et bien sûr... coder !
Cela fait maintenant plusieurs années que j'évolue dans le monde du développement frontend, à l'aide de l'incontournable framework librairie React. Comme bien souvent, pour instancier un projet viable rapidement (sans se soucier de la configuration Webpack), j'utilise l'outil de ligne de commande create-react-app
. Malheureusement, à chaque fois, il me manque quelque chose, à savoir : une dépendance, un format de fichier non supporté (par défaut), ou encore une architecture projet pour les applications à l'échelle. J'ai donc décidé de créer mon propre projet clé en main pour mes développements futurs. Dans cet article, je vous propose de suivre, étape par étape, l'élaboration de ce que l'on appelle communément un "boilerplate" pour le framework la librairie React.
NB : Avant d'entrer dans le vif du sujet, il faut savoir que j'ai passé beaucoup de temps sur Figma afin de designer des SVGs indispensables à ma base de développement (en particulier, j'ai choisi le jeu Monument Valley comme thème graphique). J'en parlerai peut-être plus dans un article dérivé...
1er Étape : Initialisation Du Projet
Le moyen le plus simple pour créer un nouveau projet React est d'utiliser l'outil de ligne de commande :
npx create-react-app my-awesome-project
Encore une fois, create-react-app
facile grandement les choses. Il permet de générer une arborescence de projet React incluant les dépendances principales (react
et react-dom
), tout en mystifiant certaines dépendances techniques (Webpack, Jest, ESLint, etc...) ainsi que leur fichier de configuration. Lorsqu'on choisit ce mode de fonctionnement pour générer du code, toute la configuration de notre application repose sur la dépendance react-scripts
. Néanmoins, son fonctionnement permet au développeur de surcharger certaines librairies aisément, c'est le cas pour ESLint.
Les dernières versions de react-scripts
vont même plus loin, puisque la librairie est dorénavant pré-configurée pour accepter des dépendances additionnelles sans configuration supplémentaire. C'est le cas pour la prise en charge du langage Sass, ou encore pour l'implémentation du sur-ensemble JavaScript typé : TypeScript. Pour ma part, j'ai choisi d'utiliser le format .scss
dans mon projet clé en main, simplement en ajoutant le paquet qui convient :
npm install node-sass
NB : Si vous souhaitez voir ce qui se cache derrière la dépendance react-scripts
, je vous invite à éjecter le projet via la commande ci-dessous. Attention toutefois, cette action est irréversible...
npm run eject
2ème Étape : Ajout De Dépendances
J'ai déjà commencé à ajouter des dépendances avec node-sass
. Super ! Grâce à cela, je vais déjà pouvoir éditer du .scss
et l'importer directement dans mes fichiers .js
et .jsx
.
Ensuite, j'ai souhaité mettre en place des règles de "linting", afin de rendre mon code plus propre (bien que ceci soit une notion subjective) mais surtout qu'il soit correctement formaté. J'ai donc ajouté tout ce qui est relatif à la dépendance Prettier :
npm install prettier eslint-config-prettier eslint-plugin-prettier
Sachant que react-scripts
embarque déjà ESLint, je n'ai pas eu besoin d'ajouter cette dépendance. En revanche, il a fallu que j'édite le package.json
de mon projet :
- Pour ajouter le script
lint
- Pour compléter la configuration d'ESLint
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"lint": "eslint --ext .js,.jsx src"
},
"eslintConfig": {
"extends": ["react-app", "prettier", "prettier/react"],
"plugins": ["prettier"]
}
Pour le formateur, je ne me suis pas arrêté là. Puisque j'ai décidé d'avoir une configuration précise pour le formatage de mon code, j'ai dû créer un fichier de configuration Prettier (prettier.config.js
) avec l'ensemble de ces options :
module.exports = {
printWidth: 120,
singleQuote: true,
trailingComma: 'none',
jsxBracketSameLine: true,
arrowParens: 'avoid'
};
Enfin, j'ai ajouté cette même configuration au niveau d'ESLint :
"eslintConfig": {
"extends": ["react-app", "prettier", "prettier/react"],
"plugins": ["prettier"],
"rules": {
"prettier/prettier": [
"warn",
{
"printWidth": 120,
"singleQuote": true,
"trailingComma": "none",
"jsxBracketSameLine": true,
"arrowParens": "avoid"
}
]
}
}
Ainsi, avec le bon plugin dans VSCode (Prettier - Code Formatter), Prettier formate le code lors de l'enregistrement des fichiers, et ESLint contrôle le formatage. C.Q.F.D !
NB : Vous pouvez aussi externaliser les paramètres ESLint, mais je n'ai pas trouvé le moyen les supprimer (ou d'empêcher leurs écritures) dans le package.json
lors de l'utilisation du template, pour ne garder que le fichier .eslintrc.js
... Je vous conseille donc de les conserver dans le package.json
au risque d'avoir deux configurations d'ESLint distinctes.
Pour en finir avec les dépendances nécessaires à mon projet clé en main ; dernier ajout : prop-types
! Vous le savez surement déjà, mais il est primordial de contrôler le typage des propriétés des composants lors de vos développements (notamment pour les gros projets collaboratifs). React a choisi d'extraire ce paquet du coeur de sa librairie à partir de la version 15.5, mais je continue de l'utiliser comme étant une bonne pratique !
npm install prop-types
NB : À propos des tests unitaires, j'ai choisi de conserver les dépendances relatives à @testing-library
dans mon package.json
. Il faut savoir que lors de l'utilisation du template, ces paquets disparaissent, seul react
, react-dom
et react-scripts
persistent. Donc, autant conserver ces librairies de tests pour la suite.
Bonus
Une fois le processus de linting en place, je me suis dit qu'il serait intéressant de l'automatiser, pour ne pas avoir à exécuter la commande npm run lint
, seulement en cas de véritable besoin. C'est ainsi que je suis tombé sur deux nouvelles trouvailles :
npm install husky lint-staged
La combinaison de ces deux dépendances me permet ainsi de jouer la tâche de formatage et de vérification de la syntaxe de mon code à chaque commit. Pour cela, un peu de configuration s'impose au sein du fichier package.json
:
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{js,jsx}": [
"prettier --write",
"eslint --fix"
]
}
3ème Étape : Organisation Du Code
J'ai profité de la mise en place de mon projet clé en main pour revoir l'organisation du code. Voici la structure générée par create-react-app
:
+-- public/ # 'index.html' Is Here
+-- src/
+-- App.css
+-- App.js
+-- App.test.js
+-- index.css
+-- index.js
+-- logo.svg
+-- serviceWorker.js
+-- setupTests.js
+-- .gitignore
+-- package.json
J'apprécie davantage mon projet lorsqu'il est organisé par dossiers de composants. De même, je considère un fichier de composant comme étant suffixé par l'extension .jsx
et non .js
(même si cette assertion est aussi valable), visuellement je retrouve plus rapidement mes composants. Voici la structure que je vous propose :
+-- public/ # 'index.html' Is Here
+-- src/
+-- components/
+-- __tests__
+-- App.test.js
+-- Content.test.js
+-- vectors/ # SVGs Are Here
+-- App.jsx
+-- Content.jsx
+-- index.js
+-- index.scss
+-- setupTests.js
+-- .gitignore
+-- package.json
+-- prettier.config.js
On note la disparition du fichier serviceWorker.js
. En effet, dans mon cas, je n'ai pas eu besoin de mettre en oeuvre de la PWA, j'ai donc préféré enlever cette partie pour alléger le code. De plus, on s'aperçoit très vite de l'apparition du dossier __tests__
qui me permet de regrouper mes tests unitaires (d'un même répertoire), au sein d'un unique endroit.
Je suis conscient que mon organisation peut paraître un peu trop directive, et ainsi laisser moins de liberté lors du développement d'applications, mais ce n'est qu'une proposition. Après tout, la base fournie par create-react-app
est suffisamment "plate" pour en faire ce que l'on souhaite !
4ème Étape : Composition Du Template
Une fois votre projet React fonctionnel et satisfaisant (personnellement, j'ai changé toute la partie graphique), vous pourrez entamer la création du template. Pour cela, quelques règles s'imposent :
- Le template doit être nommé selon une convention (par exemple,
cra-template-my-awesome-project
) - Le template doit respecter une architecture spécifique
+-- cra-template-my-awesome-project/
+-- template/ # Previous Project Goes Here
+-- public/
+-- src/
+-- gitignore
+-- README.md
+-- package.json
+-- README.md
+-- template.json
La structure ci-dessus décrit l'organisation du template. Dites-vous simplement que votre précédent projet est ni plus ni moins que le répertoire template
(il doit conserver ce libellé, c'est primordial).
NB : De même, le fichier .gitignore
doit être renommé gitignore
, sinon le template ne pourra pas fonctionner.
D'après l'organisation du code que je vous ai présentée précédemment, la structure finale devrait ressembler à quelque chose comme cela :
+-- cra-template-my-awesome-project/
+-- template/
+-- public/ # 'index.html' Is Here
+-- src/
+-- components/
+-- __tests__
+-- App.test.js
+-- Content.test.js
+-- vectors/ # SVGs Are Here
+-- App.jsx
+-- Content.jsx
+-- gitignore
+-- index.js
+-- index.scss
+-- prettier.config.js
+-- setupTests.js
+-- package.json
+-- README.md
+-- template.json
Une fois ces changements opérés vous devrez déplacer les dépendances, configurations et scripts (sauf ceux en rapport avec react-scripts
) présents dans votre package.json
vers le fichier template.json
de la manière suivante :
{
"package": {
"dependencies": {
"@testing-library/jest-dom": "~4.2.0",
"@testing-library/react": "~9.3.0",
"eslint-config-prettier": "~6.10.0",
"eslint-plugin-prettier": "~3.1.0",
"husky": "~4.2.0",
"lint-staged": "~10.1.0",
"node-sass": "~4.13.0",
"prettier": "~2.0.0",
"prop-types": "~15.7.0"
},
"scripts": {
"lint": "eslint --ext .js,.jsx src"
},
"eslintConfig": {
"extends": ["react-app", "prettier", "prettier/react"],
"plugins": ["prettier"],
"rules": {
"prettier/prettier": [
"warn",
{
"printWidth": 120,
"singleQuote": true,
"trailingComma": "none",
"jsxBracketSameLine": true,
"arrowParens": "avoid"
}
]
}
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{js,jsx}": ["prettier --write", "eslint --fix"]
}
}
}
N'oubliez pas de renseigner le package.json
pour votre futur(e) template / librairie pour create-react-app
, ainsi que les deux fichiers README.md
(un pour votre dépendance NPM, l'autre pour le projet généré par ce template). Il reste dorénavant à tester votre template en local. Pour cela, il suffit de faire pointer l'usage de l'outil de ligne de commande vers votre répertoire cra-template-my-awesome-project
, et d'observer le résultat :
npx create-react-app my-awesome-project --template file:./path/to/cra-template-my-awesome-project
cd my-awesome-project
npm run start
Et voilà ! Si le résultat final vous satisfait pleinement, il ne vous reste qu'à publier votre template sur NPM. Pour ma part, vous retrouverez mon template embarquant toutes les librairies présentées ci-dessus, et répondant au visuel ci-dessous, directement sur NPM (le code source est disponible sur GitLab).
J'ai pris du plaisir à faire ce projet (d'ailleurs plus sur la partie design que sur le code en soi). Les possibilités qu'offre l'outil de ligne de commande sont de plus en plus variées (à noter que les templates personnalisés ne sont disponibles qu'à partir de la version 3.3.0 de la dépendance react-scripts
). Je prévois de (re)faire d'autres boilerplates pour des cas d'usages professionnels cette fois-ci. Par exemple, un projet clé en main avec un certain nombre de hooks personnalisés pré-instanciés, ou encore un autre boilerplate qui implémente une base Redux pour des applications à l'échelle.
Compte tenu de l'orientation que prend React ces dernières années (avec notamment la naissance des hooks, ou encore avec la souplesse de create-react-app
), la librairie JavaScript pour créer des interfaces se révèle être mon choix premier lorsque je dois construire une nouvelle application Web ; c'est peut-être aussi mon amour pour la programmation fonctionnelle qui me fait penser cela... Quoi qu'il en soit, j'espère un jour voir certains de ces concepts embarqués dans d'autres frameworks orientés composants (peut-être Vue, peut-être Svelte). Affaire à suivre...