Architecture client
Alias du thĂšmeâ
Un thĂšme fonctionne en exportant un ensemble de composants, par exemple Navbar
, Layout
, Footer
, pour rendre les données transmises à partir de plugins. Docusaurus et les utilisateurs utilisent ces composants en les important en utilisant l'alias de webpack @theme
:
import Navbar from '@theme/Navbar';
L'alias @theme
peut faire référence à quelques répertoires, selon la priorité suivante :
- Un répertoire
website/src/theme
d'un utilisateur, qui est un répertoire spécial qui a la priorité supérieure. - Un répertoire
theme
des paquets du thÚme de Docusaurus. - Des composants de secours fournis par le noyau de Docusaurus (généralement pas nécessaire).
C'est ce qu'on appelle une architecture en couches : une couche de priorité supérieure fournissant le composant ferait de l'ombre à une couche de priorité inférieure, rendant le swizzling possible. Considérons la structure suivante :
website
âââ node_modules
â âââ @docusaurus/theme-classic
â âââ theme
â âââ Navbar.js
âââ src
âââ theme
âââ Navbar.js
website/src/theme/Navbar.js
est prioritaire lorsque @theme/Navbar
est importĂ©. Ce comportement est appelĂ© « swizzling de composant ». Si vous ĂȘtes familier avec Objective C oĂč l'implĂ©mentation d'une fonction peut ĂȘtre permutĂ©e pendant l'exĂ©cution, c'est exactement le mĂȘme concept ici avec le changement de la cible vers laquelle @theme/Navbar
pointe !
Nous avons déjà parlé de la façon dont le « thÚme personnalisé » dans src/theme
peut réutiliser un composant de thÚme grùce à l'alias @theme-original
. Un paquet de thÚme peut également envelopper un composant d'un autre thÚme, en important le composant du thÚme initial, à l'aide de l'import @theme-init
.
Voici un exemple d'utilisation de cette fonctionnalité pour améliorer le composant CodeBlock
du thÚme par défaut avec une fonctionnalité de terrain de jeu react-live
.
import InitialCodeBlock from '@theme-init/CodeBlock';
import React from 'react';
export default function CodeBlock(props) {
return props.live ? (
<ReactLivePlayground {...props} />
) : (
<InitialCodeBlock {...props} />
);
}
Vérifiez le code de @docusaurus/theme-live-codeblock
pour plus de détails.
à moins que vous ne vouliez publier sur npm un « rehausseur de thÚme » (comme @docusaurus-theme-live-codeblock
), vous n'avez probablement pas besoin de @theme-init
.
Il peut ĂȘtre assez difficile de se faire une idĂ©e de ces alias. Imaginons le cas suivant avec une configuration super alambiquĂ©e oĂč trois thĂšmes/plugins et le site lui-mĂȘme essaient tous de dĂ©finir le mĂȘme composant. En interne, Docusaurus charge ces thĂšmes comme une « pile ».
+-------------------------------------------------+
| `website/src/theme/CodeBlock.js` | <-- `@theme/CodeBlock` est toujours placé en haut
+-------------------------------------------------+
| `theme-live-codeblock/theme/CodeBlock/index.js` | <-- `@theme-original/CodeBlock` le composant non swizzlé placé le plus haut
+-------------------------------------------------+
| `plugin-awesome-codeblock/theme/CodeBlock.js` |
+-------------------------------------------------+
| `theme-classic/theme/CodeBlock/index.js` | <-- `@theme-init/CodeBlock` est toujours placé en bas
+-------------------------------------------------+
Les composants de cette « pile » sont poussés dans l'ordre de preset plugins > preset themes > plugins > themes > site
, de sorte que le composant swizzlé dans website/src/theme
soit toujours placé en haut car il est chargé en dernier.
@theme/*
est toujours placé au niveau le plus haut du composant - lorsque CodeBlock
est modifié, tous les autres composants qui demandent @theme/CodeBlock
reçoivent la version modifiée.
@theme-original/* est toujours placé au plus haut du composant non swizzlé. C'est la raison pour laquelle vous pouvez importer
@theme-original/CodeBlock dans le composant swizzlĂ©` - il pointe vers le suivant dans la « pile de composants », un composant fourni par le thĂšme. Les auteurs de plugins ne devraient pas essayer de l'utiliser car votre composant pourrait ĂȘtre le composant le plus haut et provoquer un auto-import.
@theme-init/* est toujours placé en bas - généralement, il provient du thÚme ou du plugin qui fournit ce composant en premier. Les plugins / thÚmes individuels qui tentent d'améliorer le bloc de code peuvent utiliser en toute sécurité
@theme-init/CodeBlockpour obtenir sa version de base. Les créateurs de sites ne devraient généralement pas l'utiliser car vous souhaitez probablement améliorer le composant le _plus haut_ au lieu du _plus bas_. Il est également possible que l'alias
@theme-init/CodeBlockn'existe pas du tout â Docusaurus ne le crĂ©e que lorsqu'il pointe vers un alias diffĂ©rent de
@theme-original/CodeBlock`, c'est-Ă -dire lorsqu'il est fourni par plus d'un thĂšme. Nous ne gaspillons pas les alias !
Modules clientsâ
Les modules clients font partie de l'offre groupĂ©e de votre site, tout comme les composants du thĂšme. Cependant, ils sont gĂ©nĂ©ralement accompagnĂ©s d'effets secondaires. Les modules clients peuvent ĂȘtre import
Ă©s par Webpack â CSS, JS, etc. Les scripts JS travaillent gĂ©nĂ©ralement sur le contexte global, comme l'enregistrement d'Ă©couteurs d'Ă©vĂ©nements, la crĂ©ation de variables globales...
Ces modules sont importĂ©s globalement avant mĂȘme que React ne rende l'interface utilisateur initiale.
// Comment ça marche sous le capot
import '@generated/client-modules';
Les plugins et les sites peuvent déclarer les modules clients respectivement via getClientModules
et siteConfig.clientModules
.
Les modules clients sont également appelés pendant le rendu cÎté serveur, alors n'oubliez pas de vérifier l'environnement d'exécution avant d'accéder aux globales cÎté client.
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
if (ExecutionEnvironment.canUseDOM) {
// DÚs que le site se charge dans le navigateur, cela enregistre une écoute d'événements globale
window.addEventListener('keydown', (e) => {
if (e.code === 'Period') {
location.assign(location.href.replace('.com', '.dev'));
}
});
}
Les feuilles de style CSS importées en tant que modules clients sont globales.
/* Cette feuille de style est globale. */
.globalSelector {
color: red;
}
Cycles de vie du module clientâ
En dehors de l'introduction d'effets secondaires, les modules clients peuvent éventuellement exporter deux fonctions de cycle de vie : onRouteUpdate
et onRouteDidUpdate
.
Comme Docusaurus construit une application mono-page, les balises script
ne seront exécutées que lors du premier chargement de la page, mais ne seront pas ré-exécutées lors des transitions de page. Ces cycles de vie sont utiles si vous avez une logique JS impérative qui doit s'exécuter chaque fois qu'une nouvelle page est chargée, par exemple pour manipuler des éléments DOM, pour envoyer des données analytiques, etc.
Pour chaque transition de route, il y aura plusieurs moments importants :
- L'utilisateur clique sur un lien, ce qui fait que le routeur change son emplacement actuel.
- Docusaurus précharge les ressources de la route suivante, tout en conservant l'affichage du contenu de la page courante.
- Les ressources de la route suivante ont été chargées.
- Le composant de la route du nouvel emplacement est rendu dans le DOM.
onRouteUpdate
sera appelé à l'événement (2), et onRouteDidUpdate
sera appelĂ© au (4). Ils reçoivent tous deux l'emplacement actuel et l'emplacement prĂ©cĂ©dent (qui peut ĂȘtre null
, si c'est le premier écran).
onRouteUpdate
peut optionnellement retourner un callback « cleanup », qui sera appelé au (3). Par exemple, si vous voulez afficher une barre de progression, vous pouvez démarrer un délai dans onRouteUpdate
, et effacer le délai d'attente dans le callback. (Le thÚme classique fournit déjà une intégration nprogress
de cette maniĂšre.)
Notez que le DOM de la nouvelle page n'est disponible que pendant l'événement (4). Si vous avez besoin de manipuler le DOM de la nouvelle page, vous voudrez probablement utiliser onRouteDidUpdate
, qui sera lancé dÚs que le DOM sur la nouvelle page sera monté.
export function onRouteDidUpdate({location, previousLocation}) {
// Ne pas exĂ©cuter si nous sommes toujours sur la mĂȘme page ; le cycle de vie peut ĂȘtre dĂ©clenchĂ©
// parce que le hachage change (par exemple, lors de la navigation entre les titres)
if (location.pathname !== previousLocation?.pathname) {
const title = document.getElementsByTagName('h1')[0];
if (title) {
title.innerText += 'â€ïž';
}
}
}
export function onRouteUpdate({location, previousLocation}) {
if (location.pathname !== previousLocation?.pathname) {
const progressBarTimeout = window.setTimeout(() => {
nprogress.start();
}, delay);
return () => window.clearTimeout(progressBarTimeout);
}
return undefined;
}
Ou, si vous utilisez TypeScript et que vous souhaitez tirer parti du typage contextuel :
import type {ClientModule} from '@docusaurus/types';
const module: ClientModule = {
onRouteUpdate({location, previousLocation}) {
// ...
},
onRouteDidUpdate({location, previousLocation}) {
// ...
},
};
export default module;
Les deux cycles de vie se déclencheront au premier rendu, mais pas du cÎté serveur, de sorte que vous pouvez y accéder sans risque aux globaux du navigateur.
Les cycles de vie des modules clients sont purement impératifs, et vous ne pouvez pas utiliser les hooks React ou accéder aux contextes React en leur sein. Si vos opérations sont pilotées par l'état ou impliquent des manipulations DOM compliquées, vous devriez plutÎt envisager de swizzler les composants.