Pour les thèmes et les plugins avancés, Discourse propose le système modifyClass. Celui-ci vous permet d’étendre et de remplacer la fonctionnalité dans de nombreuses classes javascript de base.
Quand utiliser modifyClass
modifyClass doit être un dernier recours, lorsque votre personnalisation ne peut pas être effectuée via les API de personnalisation plus stables de Discourse (par exemple, les méthodes de l’API des plugins, les sorties de plugins, les transformateurs).
Le code de base peut changer à tout moment. Par conséquent, les personnalisations effectuées via modifyClass peuvent cesser de fonctionner à tout moment. Lors de l’utilisation de cette API, vous devez vous assurer que vous disposez de contrôles pour détecter ces problèmes avant qu’ils n’atteignent un site de production. Par exemple, vous pourriez ajouter des tests automatisés au thème/plugin, ou utiliser un site de staging pour tester les mises à jour entrantes de Discourse par rapport à votre thème/plugin.
Utilisation de base
api.modifyClass peut être utilisé pour modifier les fonctions et les propriétés de toute classe accessible via le résolveur Ember. Cela inclut les routes, les contrôleurs, les services et les composants de Discourse.
modifyClass prend deux arguments :
-
resolverName(chaîne de caractères) - construisez-le en utilisant le type (par exemple,component/controller/etc.), suivi de deux-points, suivi du nom de la classe (avec des traits d’union). Par exemple :component:d-button,component:modal/login,controller:user,route:application, etc. -
callback(fonction) - une fonction qui reçoit la définition de classe existante, puis retourne une version étendue.
Par exemple, pour modifier l’action click() sur d-button:
api.modifyClass(
"component:d-button",
(Superclass) =>
class extends Superclass {
@action
click() {
console.log("button was clicked");
super.click();
}
}
);
La syntaxe class extends ... imite celle des classes enfants JS. En général, toute syntaxe/fonctionnalité prise en charge par les classes enfants peut être appliquée ici. Cela inclut super, les propriétés/fonctions statiques, et plus encore.
Cependant, il existe certaines limitations. Le système modifyClass ne détecte que les modifications apportées au prototype JS de la classe. Concrètement, cela signifie :
-
l’introduction ou la modification d’un
constructor()n’est pas prise en chargeapi.modifyClass( "component:foo", (Superclass) => class extends Superclass { constructor() { // Ceci n'est pas pris en charge. Le constructeur sera ignoré } } ); -
l’introduction ou la modification des champs de classe ne sont pas prises en charge (bien que certains champs de classe décorés, comme
@tracked, puissent être utilisés)api.modifyClass( "component:foo", (Superclass) => class extends Superclass { someField = "foo"; // NON PRIS EN CHARGE - ne pas copier @tracked someOtherField = "foo"; // Ceci est acceptable } ); -
les champs de classe simples sur l’implémentation originale ne peuvent pas être remplacés d’aucune manière (bien que, comme ci-dessus, les champs
@trackedpuissent être remplacés par un autre champ@tracked)// Code de base : class Foo extends Component { // Ce champ de base ne peut pas être remplacé someField = "original"; // Ce champ tracked de base peut être remplacé en incluant // `@tracked someTrackedField =` dans l'appel modifyClass @tracked someTrackedField = "original"; }
Si vous vous trouvez à vouloir faire ces choses, votre cas d’utilisation pourrait être mieux satisfait en créant une PR pour introduire de nouvelles API dans le code de base (par exemple, des sorties de plugins, des transformateurs ou des API sur mesure).
Mise à niveau de la syntaxe héritée
Dans le passé, modifyClass était appelé en utilisant une syntaxe de littéral d’objet comme ceci :
// Syntaxe obsolète - ne pas utiliser
api.modifyClass("component:some-component", {
someFunction() {
const original = this._super();
return original + " some change";
}
pluginId: "some-unique-id"
});
Cette syntaxe n’est plus recommandée et présente des bogues connus (par exemple, remplacer les accesseurs ou les @actions). Tout code utilisant cette syntaxe doit être mis à jour pour utiliser la syntaxe de classe native décrite ci-dessus. En général, la conversion peut être effectuée en :
- supprimant
pluginId- ceci n’est plus requis - Mise à jour vers la syntaxe de classe native moderne décrite ci-dessus
- Tester vos changements
Dépannage
Classe déjà initialisée
Lors de l’utilisation de modifyClass dans un initialiseur, vous pouvez voir cet avertissement dans la console :
Attempted to modify "{name}", but it was already initialized earlier in the boot process
Dans le développement de thèmes/plugins, deux façons introduisent normalement cette erreur :
-
L’ajout d’un
lookup()a provoqué l’erreurSi vous
lookup()un singleton trop tôt dans le processus de démarrage, cela fera échouer tous les appelsmodifyClassultérieurs. Dans ce cas, vous devriez essayer de faire en sorte que la recherche ait lieu plus tard. Par exemple, vous changeriez quelque chose comme ceci :// Recherche du service dans l'initialiseur, puis utilisation à l'exécution (mauvais !) export default apiInitializer((api) => { const composerService = api.container.lookup("service:composer"); api.composerBeforeSave(async () => { composerService.doSomething(); }); });En ceci :
// Recherche de service 'Juste à temps' (bon !) export default apiInitializer((api) => { api.composerBeforeSave(async () => { const composerService = api.container.lookup("service:composer"); composerService.doSomething(); }); }); -
L’ajout d’un nouveau
modifyClassa provoqué l’erreurSi l’erreur est introduite par votre thème/plugin ajoutant un appel
modifyClass, alors vous devrez le déplacer plus tôt dans le processus de démarrage. Cela se produit généralement lors de la modification des méthodes sur les services (par exemple,topicTrackingState), et sur les modèles qui sont initialisés tôt dans le processus de démarrage de l’application (par exemple, unmodel:userest initialisé pourservice:current-user).Déplacer l’appel
modifyClassplus tôt dans le processus de démarrage signifie normalement déplacer l’appel vers unpre-initializer, et le configurer pour qu’il s’exécute avant l’initialiseur ‘inject-discourse-objects’ de Discourse. Par exemple :// (plugin)/assets/javascripts/discourse/pre-initializers/extend-user-for-my-plugin.js // ou // (theme)/javascripts/discourse/pre-initializers/extend-user-for-my-plugin.js import { withPluginApi } from "discourse/lib/plugin-api"; export default { name: "extend-user-for-my-plugin", before: "inject-discourse-objects", initializeWithApi(api) { api.modifyClass("model:user", (Superclass) => class extends Superclass { myNewUserFunction() { return "hello world"; }, }); }, initialize() { withPluginApi(this.initializeWithApi); }, };Cette modification du modèle utilisateur devrait maintenant fonctionner sans afficher d’avertissement, et la nouvelle méthode sera disponible sur l’objet
currentUser.
Ce document est contrôlé par version - suggérez des modifications sur github.