Chapitre 7.1 — Le frontend Moodle expliqué à un développeur React
Ce chapitre est une carte mentale. Avant d’écrire la moindre ligne de JavaScript pour Moodle, vous devez comprendre qui possède le DOM, où vit l’état, et ce que le core fournit déjà. Si vous abordez Moodle avec les réflexes d’une SPA React — « je monte mon app, je fetch mes données, je rends tout côté client » — vous allez produire du code qui fonctionne en apparence mais qui casse l’accessibilité, la thémabilité, le cache, la navigation, et qui sera un cauchemar à maintenir. À l’inverse, si vous comprenez le modèle, vous découvrirez qu’il ressemble beaucoup à ce que vous pratiquez déjà avec l’App Router de Next.js : du rendu serveur par défaut, et du JavaScript client uniquement là où l’interactivité le justifie.
1. Vingt ans d’histoire en accéléré : pourquoi le frontend Moodle est ce qu’il est
Moodle existe depuis 2002. Son frontend porte les strates géologiques de chaque époque du web, et vous croiserez encore des fossiles de chacune. Comprendre l’histoire, c’est comprendre pourquoi trois systèmes de modules JavaScript coexistent dans le même dépôt.
1.1 L’ère YUI (2008 – ~2014)
Moodle 2.0 a standardisé son JavaScript sur YUI (Yahoo! User Interface Library), une bibliothèque à l’époque concurrente de jQuery, avec son propre système de modules (Y.use(...)), ses widgets, son loader. Yahoo a abandonné YUI en 2014, laissant Moodle avec des dizaines de milliers de lignes orphelines.
État en 5.2 : YUI est en fin de vie active. Le core en retire méthodiquement les derniers usages à chaque version (5.2 a par exemple supprimé le TreeView YUI du module Dossier). Il reste des poches résiduelles (certains éditeurs de vieux plugins, le drag & drop historique de certaines zones). Règle absolue : n’écrivez jamais de nouveau code YUI. Si vous voyez Y.use ou un dossier yui/ dans un plugin, c’est un signal de dette technique.
1.2 L’ère jQuery + AMD/RequireJS (2014 – ~2018)
Moodle 2.9 a introduit le format AMD (Asynchronous Module Definition) chargé par RequireJS, avec jQuery comme bibliothèque DOM de référence. Les modules s’écrivaient ainsi :
// Style historique (Moodle 2.9 – 3.7) — NE PLUS ÉCRIRE AINSI
define(['jquery', 'core/str'], function($, Str) {
return {
init: function(selector) {
$(selector).on('click', function() { /* ... */ });
}
};
});C’était le CommonJS/UMD de l’époque navigateur : des modules asynchrones, un loader, un graphe de dépendances. jQuery servait de couche de compatibilité DOM.
État en 5.2 : le runtime AMD est toujours là — c’est RequireJS qui charge tous les modules en production — mais on n’écrit plus de define() à la main. jQuery est toujours livré (des centaines de modules core et tiers en dépendent), mais son usage dans du code nouveau est interdit dans le core et fortement déconseillé partout ailleurs : le DOM natif (querySelector, addEventListener, fetch, classList) couvre tout.
1.3 L’ère ES6-transpilé-en-AMD (Moodle 3.8 → aujourd’hui)
Depuis Moodle 3.8 (et obligatoirement pour le core depuis 3.10), les modules s’écrivent en syntaxe ESM moderne (import / export, classes, async/await, destructuring) dans amd/src/, puis Babel les transpile en AMD minifié dans amd/build/ via Grunt :
// Style actuel (amd/src/hello.js) — syntaxe ESM…
import {getString} from 'core/str';
import Notification from 'core/notification';
export const init = async (selector) => {
const node = document.querySelector(selector);
const label = await getString('hello', 'local_monplugin');
node.textContent = label;
};…mais après npx grunt amd, le fichier amd/build/hello.min.js contient un define(['exports', 'core/str', 'core/notification'], ...) : du AMD. Le navigateur ne voit jamais d’import natif. C’est le point le plus contre-intuitif de toute la toolchain : vous écrivez de l’ESM, le navigateur exécute de l’AMD. Nous détaillerons la mécanique au chapitre 7.3.
1.4 L’état exact en Moodle 5.2
Photographie de la toolchain au moment où vous lisez ceci :
| Brique | État en 5.2 |
|---|---|
| Bootstrap | Bootstrap 5 (migration 4→5 achevée en 5.x ; les classes BS4 dépréciées sont retirées progressivement). Fournit la grille, les composants (dropdowns, collapse, tooltips…) et les utilities. |
| Éditeur | TinyMCE est l’éditeur par défaut (Atto est déprécié/retiré). |
| Modules JS plugin | ESM dans amd/src/ → Babel/Grunt → AMD minifié dans amd/build/ → chargé par RequireJS. C’est la voie standard pour les plugins. |
| jQuery | Présent pour compatibilité, proscrit dans le nouveau code. |
| YUI | Résiduel, en cours d’extraction, interdit pour du nouveau code. |
| Promesses | Natives depuis 4.3 (les .done()/.fail() jQuery sont dépréciés). |
| React | Nouveauté 5.2 : React entre dans le core comme bundle externe, avec un système d’importmaps, un helper Mustache {{#react}} avec auto-initialisation, des sources TypeScript dans <component>/js/esm/src/, une tâche grunt react, et la phase 1 du Moodle Design System (design tokens). C’est le début d’une transition, pas un remplacement : l’écrasante majorité de l’UI reste Mustache + AMD. |
| ESM natif navigateur | En transition : le core commence à exposer certaines API (chaînes, web services…) sous forme de modules ESM consommables via importmap, en parallèle du monde AMD. Les deux mondes coexistent et coexisteront pendant des années. |
| Build | Node.js + Grunt (npx grunt amd, npx grunt watch, npx grunt react), ESLint pour le lint. |
💡 Pour un dev React : retenez la frise ainsi — YUI ≈ Backbone (mort), jQuery/AMD ≈ l’ère pré-webpack, ES6-buildé-AMD ≈ « on a adopté la syntaxe moderne mais gardé l’ancien runtime pour ne rien casser », et 5.2 ≈ « React et l’ESM natif entrent officiellement, en douceur, par la porte du core ». Moodle ne fait jamais de big bang frontend : 200 000+ sites en production et des milliers de plugins tiers l’interdisent. La rétrocompatibilité est une valeur cardinale du projet.
📚 Aller plus loin : moodledev.io/docs/5.2/guides/javascript (guide JS officiel), moodledev.io/docs/5.2/guides/frontend (le nouveau guide « Frontend Development » qui documente ESM/React/TypeScript), et les notes de version moodledev.io/general/releases/5.2 .
2. Le modèle mental : rendu serveur PHP + îlots de JavaScript
2.1 L’analogie avec l’App Router de Next.js
Voici la comparaison la plus utile de tout ce chapitre. Si vous pratiquez l’App Router avec les React Server Components, vous connaissez déjà l’architecture de Moodle — elle est simplement implémentée avec d’autres briques :
| Concept Next.js / RSC | Équivalent Moodle | Commentaire |
|---|---|---|
| Server Component (rend du HTML, zéro JS client) | Template Mustache rendu par PHP ($OUTPUT->render_from_template()) | La quasi-totalité de l’UI. Rendu à la requête, côté serveur, avec les données déjà résolues. |
"use client" + composant client | Module AMD initialisé sur un fragment de DOM | L’îlot d’interactivité. Il s’accroche à du HTML déjà rendu. |
| Props passées du serveur au client | data-* attributes dans le HTML, ou paramètres de $PAGE->requires->js_call_amd() | Sérialisation explicite, pas de magie. |
| Server Actions / route handlers | Web services (fonctions externes) appelés via core/ajax | Le pont de données unique et typé (voir Partie 6). |
revalidate / re-render serveur d’un fragment | core/fragment (re-rendre un morceau de page côté serveur et l’injecter) | Très proche de l’idée « le serveur re-rend, le client remplace ». |
| Layout / template racine | Layouts du thème (theme/boost/templates/columns2.mustache…) | Le thème possède la coquille de page. |
next/link + navigation client | N’existe pas : chaque navigation est un vrai chargement de page | Moodle est un site multi-pages classique (MPA). |
| Hydratation automatique | N’existe pas : « hydratation » manuelle, explicite, module par module | Vous décidez quoi initialiser, où, et quand. |
La différence fondamentale : dans Next.js, le framework orchestre la frontière serveur/client et l’hydratation. Dans Moodle, c’est vous qui tracez cette frontière, à la main, page par page. C’est plus verbeux, mais parfaitement prévisible.
2.2 Le cycle de vie d’une page Moodle
Déroulons ce qui se passe quand un utilisateur charge /mod/forum/view.php?id=42 :
- PHP route et contrôle :
view.phpvérifie login, capacités, charge le contexte (voir Parties 3–4). - PHP construit les données : requêtes SQL via
$DB, objets métier, puis un contexte de template (unstdClass/array sérialisable). - PHP rend le HTML :
$OUTPUT->render_from_template('mod_forum/...', $context)compile le Mustache côté serveur. Le thème peut avoir surchargé le template. - PHP enregistre les îlots JS :
$PAGE->requires->js_call_amd('mod_forum/discussion_list', 'init', [...])ou blocs{{#js}}dans les templates. Rien n’est exécuté encore : c’est une liste de courses. - La page part au navigateur : HTML complet, fonctionnel sans JavaScript (dans l’idéal — Moodle prend l’accessibilité et la dégradation gracieuse au sérieux).
- En pied de page, RequireJS charge les modules demandés (minifiés, mis en cache agressivement grâce à
$CFG->jsrev), et chaque module exécute soninit(): il retrouve « son » DOM via des sélecteurs/data-attributes et y attache les comportements. - Interactions : le JS écoute les événements, appelle des web services via
core/ajaxpour lire/écrire des données, met à jour des morceaux de DOM (souvent en re-rendant un template Mustache côté client viacore/templates— oui, Mustache tourne aussi dans le navigateur). - Navigation : clic sur un lien → nouvelle requête → tout recommence. Pas de routeur client, pas d’état persistant entre pages (hors
sessionStorage/localStorage et le cache de RequireJS).
💡 Pour un dev React : le point 6 est votre
useEffect(() => { ... }, [])global : du code qui s’exécute une fois le DOM prêt et qui « prend possession » d’un sous-arbre. La grande différence : personne ne re-rendra ce sous-arbre pour vous. Si les données changent, c’est à vous de mettre à jour le DOM (à la main, via un re-render Mustache client, viacore/fragment, ou — chapitre 7.4 — via un composant React monté là).
2.3 Pas de SPA, et c’est un choix
Moodle est une MPA (multi-page application) et le restera à horizon prévisible. Les raisons sont solides :
- Accessibilité : le HTML rendu serveur avec de vrais liens, de vrais formulaires et une vraie navigation fonctionne avec tous les lecteurs d’écran, sans gestion manuelle du focus à chaque « navigation » virtuelle. Moodle est soumis à des obligations légales d’accessibilité (WCAG) dans beaucoup de pays.
- Thémabilité : des milliers de sites surchargent templates et CSS. Un rendu serveur en templates surcharge-ables est le contrat d’extension. Une SPA avec composants compilés le briserait.
- Écosystème : ~2 000 plugins tiers injectent du HTML dans les pages. Le modèle « le DOM appartient à PHP » est le plus petit dénominateur commun qui les fait tous cohabiter.
- Performance perçue : un LMS est surtout constitué de pages de contenu (cours, ressources, forums). Le HTML serveur + cache HTTP est imbattable sur ce profil ; le coût d’une SPA (bundle, hydratation) ne s’y amortit pas.
2.4 Pas de virtual DOM côté core… sauf core/reactive
Le core ne fournit aucun virtual DOM généraliste. La mise à jour du DOM se fait :
- à la main (
element.textContent = ...,classList,insertAdjacentHTML) ; - par remplacement de fragments re-rendus (Mustache client via
core/templates.replaceNodeContents, ou HTML serveur viacore/fragment) ; - ou, pour les interfaces réellement stateful, via
core/reactive: une petite bibliothèque réactive interne (state manager centralisé + mutations + watchers + components) écrite en vanilla JS, sans virtual DOM, qui propulse l’éditeur de cours depuis Moodle 4.0. C’est le « Redux artisanal » de Moodle — chapitre 7.6. - et depuis 5.2, via React pour les nouveaux composants core qui l’adoptent (et vos plugins, chapitre 7.4).
2.5 « Le DOM appartient à PHP » : les conséquences pratiques
C’est LA règle d’or, formulons-la précisément : la structure de la page est produite par les templates serveur, potentiellement surchargés par le thème actif ; votre JavaScript est un invité qui s’accroche à cette structure sans la présumer. Conséquences concrètes :
a) Accrochez-vous à des data-* attributes, jamais à des classes CSS. Les classes appartiennent au styling et au thème ; elles peuvent changer. La convention Moodle :
<!-- Le template rend des points d'ancrage sémantiques -->
<div data-region="cart" data-course-id="42">
<button data-action="add-item">…</button>
</div>// Le JS cible ces ancres, jamais .btn-primary ou .ma-classe-css
const root = document.querySelector('[data-region="cart"]');
root.addEventListener('click', (e) => {
const btn = e.target.closest('[data-action="add-item"]');
if (!btn) {
return;
}
// ...
});b) Délégation d’événements systématique. Comme le DOM peut être remplacé sous vos pieds (re-render d’un fragment, contenu chargé en AJAX, autre plugin qui manipule la zone), on écoute sur un ancêtre stable (souvent document ou la racine de région) et on filtre avec closest(). C’est l’équivalent culturel de ce que React fait en interne avec son event system délégué — sauf qu’ici c’est explicite.
c) « Hydratation » manuelle et idempotente. Votre init() peut être appelé plusieurs fois (deux instances du bloc sur la même page, contenu ré-injecté). Protégez-vous :
export const init = (rootSelector) => {
const root = document.querySelector(rootSelector);
if (!root || root.dataset.initialised === 'true') {
return;
}
root.dataset.initialised = 'true';
// ... attacher les comportements
};d) Ne stockez pas d’état applicatif dans le DOM au-delà du nécessaire. Le DOM est un rendu, pas une base de données. Pour de l’état non trivial : variables de module, core/reactive, ou React.
e) Respectez ce que le serveur a rendu. Si vous devez cacher/montrer/désactiver, préférez les mécanismes prévus (classes utilitaires Bootstrap d-none, attributs hidden, disabled, ARIA) plutôt que de détruire et reconstruire des sous-arbres que d’autres scripts observent peut-être.
⚠️ Piège : ne sélectionnez jamais des éléments par les classes Bootstrap (
.btn,.card,.dropdown-menu) ni par la structure (div > div:nth-child(2)). Un thème enfant qui surcharge le template cassera votre plugin silencieusement. Lesdata-*attributes font partie du contrat entre template et JS ; les classes n’en font pas partie. C’est documenté et c’est un critère de revue de code dans le core.
⚠️ Piège :
DOMContentLoadedest souvent déjà passé quand votre module AMD s’exécute (les scripts sont en pied de page et chargés en asynchrone). N’enveloppez pas votreinitdans un listenerDOMContentLoadedsans testerdocument.readyState, sinon votre code ne s’exécutera jamais dans certains cas de timing. En pratique : le DOM de la page est disponible quandinit()est appelé viajs_call_amd; c’est l’une des garanties du système.
3. Où vit l’état ? (la question que tout dev React se pose)
Dans une app React, l’état vit dans les composants, un store, ou le cache d’un client de requêtes. Dans Moodle :
- L’état de vérité est en base de données, côté serveur. Toujours. Le client n’a que des projections temporaires.
- L’état de session (utilisateur connecté, préférences) : côté PHP (
$SESSION, préférences utilisateur viaset_user_preference()— mutables aussi depuis JS via le web servicecore_user_update_user_preferences). - L’état d’UI éphémère (accordéon ouvert, onglet actif) : dans le DOM (attributs ARIA, classes Bootstrap) ou en variables de module. Certaines UI core persistent ces états en préférence utilisateur pour les retrouver au rechargement (ex. sections de cours repliées).
- L’état d’UI complexe (éditeur de cours : sections, activités, drag & drop, mises à jour concurrentes) :
core/reactiveavec un state chargé au boot puis muté par messages — chapitre 7.6. - Entre les pages : rien, par conception. Chaque page recharge son état depuis le serveur. Si vous vous surprenez à vouloir un store global persistant côté client, c’est presque toujours le signe que la donnée doit être demandée au serveur (web service) ou persistée en préférence utilisateur.
💡 Pour un dev React : pensez « React Query avec
staleTime: 0et sans cache » : le serveur est toujours la source de vérité, on re-fetch à chaque page. La bonne nouvelle : pas d’invalidation de cache client à gérer. La moins bonne : chaque page repart de zéro — d’où l’importance du rendu serveur rapide et du prefetch (core/prefetch, chapitre 7.3) pour les chaînes et templates dont le JS aura besoin.
4. Inventaire : ce que le core vous donne déjà (ne le réinventez pas)
Réflexe n°1 avant d’écrire un composant : le core l’a probablement déjà. Voici l’inventaire des briques UI/JS que tout développeur Moodle doit connaître. Chaque entrée sera approfondie au chapitre 7.3 ; ici, il s’agit de savoir qu’elles existent pour ne jamais les recoder.
4.1 Composants d’interface
| Besoin | Solution core | Module / mécanisme |
|---|---|---|
| Boîte de dialogue / modale | core/modal et ses sous-classes (modal_save_cancel, modal_delete_cancel, modal_alert, modal_cancel) | Instanciation JS, gestion du focus et d’ARIA incluse |
| Formulaire complet dans une modale AJAX (avec validation serveur !) | core_form/modalform | La pépite : un moodleform PHP rendu, soumis et validé en AJAX dans une modale — chapitre 7.3 |
| Notification toast (feedback éphémère) | core/toast | addToast('Enregistré', {type: 'success'}) |
| Bandeaux d’alerte / exceptions | core/notification | Notification.exception(err) = l’error boundary de Moodle |
| Menus déroulants, accordéons, onglets, tooltips, popovers | Bootstrap 5 (bundle chargé par le thème) | Utilisez le markup BS + data-attributes ; pas de JS à écrire |
| Sélecteur avec autocomplétion (tags, recherche AJAX) | core/form-autocomplete + champ de formulaire autocomplete | Sert aussi pour la recherche d’utilisateurs, de cours… |
| Zone de dépôt de fichiers | core/dropzone (depuis 4.4) | Accessible nativement |
| Arbre accessible (navigation hiérarchique) | core/tree | Pattern ARIA tree |
| Menu d’actions (kebab / trois points) | core/local/action_menu + template core/action_menu | Rendu serveur + JS core |
| Fenêtre de sélection (activité, icône…) | modales core dédiées | Regardez comment le sélecteur d’activités est fait avant de créer le vôtre |
| Loader / indicateur de chargement | template core/loading + core/pending | core/pending marque les opérations en cours (crucial pour Behat) |
| Pagination, barre de progression, badges… | Templates core + Bootstrap | lib/templates/ regorge de briques |
4.2 Services JavaScript
| Besoin | Module | Rôle |
|---|---|---|
| Appeler le serveur (données) | core/ajax | Appel des web services — LE pont de données, chapitre 7.3 |
| Chaînes traduites | core/str | getString('save', 'core') → Promise, avec cache localStorage |
| Rendre un template Mustache côté client | core/templates | renderForPromise, replaceNodeContents — avec chargement auto des partials et du JS embarqué |
| Re-rendre un fragment PHP côté serveur | core/fragment | Pour ce qui ne peut pas être rendu client (ex. un formulaire) |
| Précharger chaînes/templates | core/prefetch | Évite les cascades de requêtes au premier clic |
| Événements inter-modules | core/pubsub, événements natifs documentés (core_form/events, etc.) | Découplage entre plugins |
| Garde-fou « modifications non enregistrées » | core_form/changechecker | Le beforeunload intelligent des formulaires |
| Raccourcis clavier | core/key (codes normalisés) | Constantes pour keydown handlers |
| Debounce/throttle | core/utils | debounce(fn, delay) |
| Copie dans le presse-papier | core/copy_to_clipboard | Déclaratif via data-attributes |
| Tri de listes par drag & drop | core/sortable_list | Accessible (clavier), chapitre 7.6 |
| Config et URLs | core/config | import Config from 'core/config' → Config.wwwroot, etc. |
| Gestion d’état réactive | core/reactive | Chapitre 7.6 |
4.3 Côté PHP : les composants renderables
N’oubliez pas que beaucoup d’UI se règle avant le JavaScript, côté PHP, avec les composants de core\output : single_button, action_menu, help_icon, pix_icon, paging_bar, notification, les form elements de moodleform, les tables (table_sql avec tri/pagination/téléchargement intégrés — zéro JS à écrire pour un tableau de données administratif !). Un développeur React a le réflexe « composant client » ; le développeur Moodle expérimenté a le réflexe « renderable serveur d’abord ».
⚠️ Piège : réimplémenter une modale « à la main » (div + overlay + z-index) est l’erreur de débutant la plus fréquente. Vous perdriez : la gestion du focus trap, la fermeture Escape, l’empilement de modales, l’ARIA, l’intégration thème. Idem pour les toasts, l’autocomplétion et le drag & drop. Le coût d’apprentissage de l’API core est toujours inférieur au coût de maintenance de votre réimplémentation.
💡 Pour un dev React : voyez le core comme votre bibliothèque de composants (votre shadcn/Radix interne) + votre couche data (votre React Query) + votre i18n (votre next-intl). La différence : tout est impérativement instancié plutôt que déclarativement composé.
Modal.create({...})au lieu de<Modal open={...}>. Le chapitre 7.3 vous donnera la grammaire complète.
5. Anatomie d’un îlot : exemple complet minimal
Pour ancrer le modèle, voici un îlot d’interactivité complet dans un plugin local_greeter — la version Moodle d’un composant client trivial. (La mécanique détaillée de chaque pièce arrive dans les chapitres 2 et 3 ; ici, voyez l’architecture.)
1. Le template serveur — l’équivalent du Server Component :
{{! local/greeter/templates/greeter.mustache }}
{{!
@template local_greeter/greeter
Carte de salutation avec bouton interactif.
Context (exemple):
{
"userid": 42,
"fullname": "Ada Lovelace",
"uniqid": "abc123"
}
}}
<div class="card" id="local-greeter-{{uniqid}}" data-region="greeter" data-userid="{{userid}}">
<div class="card-body">
<p>{{#str}} welcome, local_greeter, {{fullname}} {{/str}}</p>
<button class="btn btn-primary" data-action="greet">
{{#str}} greetme, local_greeter {{/str}}
</button>
<p data-region="message" aria-live="polite"></p>
</div>
</div>
{{#js}}
require(['local_greeter/greeter'], function(Greeter) {
Greeter.init('local-greeter-{{uniqid}}');
});
{{/js}}2. Le module client — l’équivalent du "use client" :
// local/greeter/amd/src/greeter.js
import Ajax from 'core/ajax';
import Notification from 'core/notification';
import {addToast} from 'core/toast';
/**
* Initialise un îlot de salutation.
*
* @param {string} id L'id du conteneur rendu par Mustache.
*/
export const init = (id) => {
const root = document.getElementById(id);
if (!root || root.dataset.initialised) {
return;
}
root.dataset.initialised = 'true';
// Délégation : un seul listener sur la racine de l'îlot.
root.addEventListener('click', async (e) => {
const button = e.target.closest('[data-action="greet"]');
if (!button) {
return;
}
button.disabled = true;
try {
// Le pont de données : un web service défini côté PHP.
const response = await Ajax.call([{
methodname: 'local_greeter_get_greeting',
args: {userid: Number(root.dataset.userid)},
}])[0];
root.querySelector('[data-region="message"]').textContent = response.message;
await addToast(response.message, {type: 'success'});
} catch (err) {
Notification.exception(err);
} finally {
button.disabled = false;
}
});
};3. Le rendu côté PHP — l’équivalent de la page :
// local/greeter/index.php (extrait)
$context = (object) [
'userid' => $USER->id,
'fullname' => fullname($USER),
];
echo $OUTPUT->render_from_template('local_greeter/greeter', $context);Notez la répartition : PHP possède les données et la structure ; Mustache décrit le HTML et déclare (sans l’exécuter) le besoin en JS ; le module AMD s’accroche par id/data-attributes, parle au serveur via un web service, et donne du feedback avec les briques core. Aucune des trois couches ne connaît les détails internes des autres au-delà du contrat (contexte de template, data-attributes, signature du web service).
💡 Pour un dev React : mentalement, ce trio (template + module + web service) EST votre composant. Quand vous concevez une fonctionnalité Moodle, découpez-la ainsi dès le départ : « quel HTML serveur ? quels data-attributes de contrat ? quel(s) web service(s) ? quel module client ? ». C’est l’équivalent de votre découpage « Server Component / Client Component / Server Action ».
6. Les anti-patterns à bannir dès maintenant
Pour clore la carte mentale, la liste noire — chaque entrée correspond à un réflexe naturel de dev SPA qui se retourne contre vous dans Moodle :
- Charger React (ou tout framework) depuis un CDN dans un
<script>: casse la CSP, le mode hors-ligne, la revue de sécurité, et le cache Moodle. Le chapitre 7.4 montre les voies propres. - Construire des URL d’API à la main et utiliser
fetch()vers des scripts PHP ad hoc : contourne la sesskey, la validation de paramètres, les capacités. Utilisez les web services +core/ajax(oucore/fetchlà où disponible). Toujours. - Générer de gros blocs de HTML en JS par concaténation de strings : non traduisible, non thémable, faille XSS en puissance. Rendez un template Mustache (client ou serveur).
- Coder en dur des textes en anglais dans le JS : toute chaîne visible passe par
core/str/{{#str}}(Partie 9, i18n). - Toucher au DOM hors de votre îlot : votre plugin ne « possède » que ce que ses templates ont rendu. Modifier la navigation, le header ou le DOM d’autrui = incidents en cascade à chaque mise à jour.
- Inline
onclick="...": interdit par les standards de code (et incompatible CSP stricte). Event listeners uniquement. - Réinventer modales/toasts/dropdowns/autocomplete : voir §4.
- Écrire du
define()AMD à la main ou du jQuery dans du code neuf : la syntaxe est ESM, le style est vanilla. - Présumer du thème : ni classes CSS du thème comme sélecteurs, ni couleurs en dur (utilisez les variables/classes Bootstrap et, à terme, les design tokens du Moodle Design System).
- Oublier que JavaScript peut être lent ou absent : la page doit rester lisible et les actions critiques doivent avoir un fallback non-JS quand c’est raisonnable (vraie page de formulaire derrière une modale, par exemple).
7. Récapitulatif et suite
Ce qu’il faut retenir de ce chapitre, en cinq phrases :
- Moodle est une MPA rendue côté serveur en PHP + Mustache ; le JavaScript n’est qu’une couche d’îlots d’interactivité explicites — architecture cousine des RSC, orchestrée à la main.
- Le DOM appartient à PHP (et au thème) : le JS s’accroche via des data-attributes contractuels, avec délégation d’événements et init idempotent.
- Vous écrivez de l’ESM moderne dans
amd/src/, Grunt/Babel le transpile en AMD exécuté par RequireJS — et Moodle 5.2 ouvre en parallèle la voie React/ESM natif dans le core. - L’état vit sur le serveur ; le pont de données unique est le système de web services appelé via
core/ajax. - Le core fournit déjà modales, toasts, formulaires-en-modale, autocomplétion, drag & drop, i18n, templating client : inventoriez avant de coder.
Au chapitre suivant, nous descendons dans la première couche du triptyque : Mustache, le langage de templates qui est à Moodle ce que JSX est à React — en volontairement plus bête, et c’est précisément sa force.