Aller au contenu principal
Version : 3.7.0

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 :

  1. Un répertoire website/src/theme d'un utilisateur, qui est un répertoire spécial qui a la priorité supérieure.
  2. Un répertoire theme des paquets du thÚme de Docusaurus.
  3. 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.

attention

À 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.

@docusaurus/core/App.tsx
// 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.

mySiteGlobalJs.js
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.

mySiteGlobalCss.css
/* 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 :

  1. L'utilisateur clique sur un lien, ce qui fait que le routeur change son emplacement actuel.
  2. Docusaurus précharge les ressources de la route suivante, tout en conservant l'affichage du contenu de la page courante.
  3. Les ressources de la route suivante ont été chargées.
  4. 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é.

myClientModule.js
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 :

myClientModule.ts
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.

Préférez l'utilisation de React

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.