Présentation
Cet article est le troisième d’une série dédiée aux applications micro-frontend. Après avoir réalisé l’exercice avec VueJS et Angular, c’est au tour de ReactJS. Si vous n’avez pas encore lu les précédents articles, je vous les conseille vivement, vous découvrirez alors les premiers composants développés autour de l’API Star Wars :
Par défaut, React ne propose pas de “packager” une application sous forme de WebComponent. Nous devons donc faire appel à une librairie tierce Direflow. Cette librairie va nous mettre à disposition un CLI et un environnement de développement adapté à la création de WebComponent utilisant ReactJS.
Le code source présenté est disponible sur le repository Github d’Ineat.
Installation du CLI Direflow
Le CLI Direflow nous permet de créer une librairie de composants et de WebComponents facilement.
Installez le CLI au moyen de la commande suivante :
npm i -g direflow-cli
Création d’un nouveau projet
Le CLI Direflow permet d’initialiser un nouveau projet en une simple commande.
direflow create
À la demande du CLI, saisissez les informations suivantes :
- Choose a name for your Direflow Setup : sw-species-component
- Give your Direflow Setup a description (optional) : Ne rien ajouter
- Which language do you want to use? JavaScript
- Do you want this to be a NPM module? Yes
Une fois la structure du projet créée, accédez au dossier sw-species-component
et exécutez la commande suivante pour lancer le serveur de développement via le CLI :
cd sw-species-component && npm install && npm start
Configuration de direflow
Nous allons appliquer quelques modifications à la configuration de Direflow. Modifiez le fichier direflow-config.json
pour désactiver le chargement de React
et React-Dom
(en lazy-load) depuis un CDN.
// direflow-config.json { "build": { "componentPath": "direflow-components", "filename": "sw-species.js" }, "modules": { "react": false, "reactDOM": false } }
Supprimez le chargement des fonts en retirant les lignes suivantes du fichier src/direflow-components/sw-species-component/index.js
.
plugins: [ { name: 'font-loader', options: { google: { families: ['Advent Pro', 'Noto Sans JP'], }, }, }, ],
Création d’un service pour les appels API
Pour que notre application micro-frontend soit autonome et puisse communiquer avec une API, nous allons créer un service.
Créez un dossier nommé services
dans le dossier src/direflow-components/sw-species-component
puis dans ce nouveau dossier créez un fichier nommé api.service.js
.
// api.service.js export function getSpecies(page) { return fetch(`https://ilab-swapi-api.azurewebsites.net/api/species?page=${page}&limit=10`); } export function searchSpecies(search, page = 1) { return fetch(`https://ilab-swapi-api.azurewebsites.net/api/species/search?name=${search}&page=${page}&limit=10`); }
Ce service va permettre de communiquer avec l’API Star Wars (déjà utilisée dans les précédents articles) et va nous renvoyer la liste des espèces de la célèbre série de films.
Ce service contient deux méthodes :
getSpecies
: retourne la liste des espècessearchSpecies
: recherche une espèce
Création des différents composants de l’application
Comme pour les autres applications micro-frontend de la série, une application mono composant n’est pas la philosophie de React, ni de la séparation des couches.
Notre application va donc contenir un composant dit conteneur et 3 composants de présentation :
- Une recherche.
- Une liste des espèces.
- Une pagination.
Le composant de recherche
Créez un dossier nommé components
dans le dossier src/direflow-components/sw-species-component
puis dans ce nouveau dossier créez deux fichiers nommé SpeciesSearch.scss
et SpeciesSearch.jsx
.
// SpeciesSearch.scss .sw-search { display: flex; flex-direction: row; width: 100%; button { min-width: 6rem; margin-left: 1rem; border: 1px solid rgba(darkred, 0.7); background: darkred; color: #fff; &.btn-clear { border: 1px solid rgba(grey, 0.7); background: lightgrey; color: grey; } } input { width: 100%; height: 35px; padding-left: 1rem; border: 1px solid #eee; background: #fafafa; font-size: 1.125rem; } }
// SpeciesSearch.jsx import React from 'react'; import { func, string } from 'prop-types'; import { Styled } from 'direflow-component'; import styles from './SpeciesSearch.scss'; const SpeciesSearch = ({ search, onSearchChange, onSubmit, onClear }) => { const handleSubmit = e => { e.preventDefault(); onSubmit(); } return ( <Styled styles={styles} scoped> <form className="sw-search" onSubmit={handleSubmit} onReset={onClear}> <input type="text" name="searchField" value={search} onChange={onSearchChange} /> { search !== '' && ( <button type="reset" className="btn-clear"> Clear </button> ) } <button type="submit"> Search </button> </form> </Styled> ) } SpeciesSearch.propTypes = { search: string.isRequired, onSearchChange: func.isRequired, onSubmit: func.isRequired, onClear: func.isRequired } export default SpeciesSearch
Le composant affichant la liste des espèces
Ajoutez un nouveau composant SpeciesTile dans le dossier components
en créant deux fichiers SpeciesTile.scss
et SpeciesTile.jsx
// SpeciesTile.scss li { list-style: none; display: flex; flex-direction: row; align-items: center; justify-content: space-between; padding: 1rem 2rem; border-bottom: 1px solid #efefef; span { font-size: 1.2rem; font-weight: 500; } .btn { background: firebrick; color: white; padding: 0.25rem 0.75rem; border-radius: 0.5rem; border: 1px solid firebrick; } }
//SpeciesTile.jsx import React from 'react'; import { shape, string } from 'prop-types'; import { Styled } from 'direflow-component'; import styles from './SpeciesTile.scss'; const SpeciesTile = ({ species }) => ( <Styled styles={styles} scoped> <li> <span>{species.name}</span> <a className="btn btn-primary">Voir</a> </li> </Styled> ); SpeciesTile.propTypes = { species: shape({ name: string.isRequired }).isRequired } export default SpeciesTile
Le composant de pagination
Comme pour les composants précédents, créez deux nouveaux fichiers Pagination.scss
et Pagination.jsx
dans le dossier components
.
// Pagination.scss .pagination-wrapper { display: flex; flex-direction: row-reverse; padding: 1rem 0; ul { display: flex; margin: 0; padding: 0; flex-direction: row; align-items: center; li { list-style: none; padding: 0.25rem 0.5rem; margin: 0 0.125rem; border: 1px solid #fff; cursor: pointer; &:hover { color: #fff; background: rgba($color: #ff2200, $alpha: 0.5); border: 1px solid rgba($color: #ff2200, $alpha: 0.5); } &.current { border: 1px solid darkred; cursor: default; color: #fff; background: darkred; } } } }
// Pagination.jsx import React, { memo } from 'react'; import { number, func } from 'prop-types'; import { Styled } from 'direflow-component'; import styles from './Pagination.scss'; const Pagination = ({ currentPage, pageNumber, onChangePage }) => { const pages = new Array(pageNumber).fill(0).map((_, index) => ({ id: index + 1, text: index + 1 })); const getClassName = (id) => id === currentPage ? 'current' : null return ( <Styled styles={styles} scoped> <div className="pagination-wrapper"> <ul> { pages.map(({ id, text }) => ( <li className={getClassName(id)} key={id} onClick={() => onChangePage(id)}> <span>{text}</span> </li>)) } </ul> </div> </Styled> ) } Pagination.propTypes = { currentPage: number.isRequired, pageNumber: number.isRequired, onChangePage: func.isRequired }; export default memo(Pagination);
Le composant conteneur
Dans le dossier src/direflow-components/sw-species-component
renommez le fichier App.css
en App.scss
et remplacez son contenu.
// App.scss .sw-bloc { padding: 0rem 1rem; } .sw-list { flex: 1; ul { margin: 0; padding: 0; } }
Puis renommez le fichier App.js
en App.jsx
et remplacez également son contenu.
// App.jsx import React, { useCallback, useEffect, useState } from 'react'; import { Styled } from 'direflow-component'; import styles from './App.scss'; import SpeciesSearch from './components/SpeciesSearch'; import SpeciesTile from './components/SpeciesTile'; import Pagination from './components/Pagination'; import { getSpecies, searchSpecies } from './services/api.service'; const App = () => { const [species, setSpecies] = useState([]); const [paginationData, setPaginationData] = useState({ currentPage: 1, pageNumber: 1 }); const [search, setSearch] = useState(''); useEffect(() => { fetchCharacters() }, []) const fetchCharacters = async (page = 1) => { const response = await getSpecies(page); const data = await response.json(); setSpecies(data.results); setPaginationData({ currentPage: page, pageNumber: data.total_pages }) } const fetchSearch = async (search, page = 1) => { const response = await searchSpecies(search, page); const data = await response.json(); setSpecies(data.results); setPaginationData({ currentPage: page, pageNumber: data.total_pages }); } const onSearchChange = useCallback(event => { setSearch(event.currentTarget.value); }, []) const onSearchClear = useCallback(() => { setSearch(''); fetchCharacters(); }, []); const onChangePage = useCallback(async (event) => { setPaginationData(state => ({ ...state, currentPage: event })) if (!search) { fetchCharacters(event); } else { fetchSearch(search, event); } }, [search]); const onSearchSubmit = useCallback(async () => { if (search !== '') { fetchSearch(search); } else { fetchCharacters(); } }, [search]); return ( <Styled styles={styles} scoped> <div> <h2>Liste des espèces</h2> <div className="sw-bloc"> <SpeciesSearch search={search} onSearchChange={onSearchChange} onSubmit={onSearchSubmit} onClear={onSearchClear} /> <div className="sw-list"> <ul className="list"> { species.map(t => ( <SpeciesTile key={t.id} species={t} /> )) } </ul> </div> { paginationData.pageNumber > 1 && <Pagination onChangePage={onChangePage} {...paginationData} /> } </div> </div> </Styled> ) }; export default App;
Relancez le serveur de développement avec la commande npm run start, vous devez obtenir le résultat suivant :
Pour packager notre composant, il ne nous reste plus qu’à exécuter la commande suivante dans le terminal :
npm run build
Attention le package ne contient pas de polyfills, ils seront chargés depuis un cdn.
Utiliser le composant micro-frontend
Le WebComponent est maintenant packagé en version minifiée « Ready for Production ». Il nous reste maintenant à l’utiliser dans une page HTML classique.
Dans votre projet, créez un dossier sample
. Dans ce dossier, créez un fichier index.html
et copiez le fichier du WebComponent (sw-species.js) présent dans le dossier build.
<!-- sample/index.html --> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>SW Species Web Component</title> <base href="/"> </head> <body> <sw-species-component></sw-species-component> <script type="text/javascript" src="sw-species.js"></script> </body> </html>
Pour voir le résultat, il suffit d’exposer avec un serveur HTTP le dossier sample. (ex: HTTP-Server => https://github.com/http-party/http-server#readme)
Performance
Notre application micro-frontend fonctionne correctement. Nous pouvons l’intégrer dans une page HTML ou dans n’importe quel framework frontend.
Côté poids, pour une application simple le WebComponent généré pèse 92,6 Ko en gzip, ce qui est assez conséquent.
Conclusion
La réalisation d’applications micro-frontend avec Direflow est relativement facile. Le CLI permet d’avoir l’environnement de développement clé en main.
Avec un WebComponent pesant 92,6 kB, Direflow permet de créer une application micro-frontend intégrable dans n’importe quelle page Web sans trop l’alourdir, avec des performances comparables aux autres librairies / frameworks (VueJS et Angular).
Dans le prochain article, nous allons passer au crible une autre librairie spécialisée dans les WebComponent: LitElement, nous pourrons alors clore cette série avec petite une synthèse !
Liste des articles liés :