Für fortgeschrittene Themes und Plugins bietet Discourse das modifyClass-System. Dies ermöglicht es Ihnen, die Funktionalität vieler JavaScript-Klassen des Kerns zu erweitern und zu überschreiben.
Wann modifyClass verwendet werden sollte
modifyClass sollte als letztes Mittel eingesetzt werden, wenn Ihre Anpassung nicht über die stabileren Anpassungs-APIs von Discourse (z. B. plugin-api-Methoden, plugin outlets, transformers) vorgenommen werden kann.
Der Kerncode kann sich jederzeit ändern. Daher könnten Anpassungen, die über modifyClass vorgenommen werden, jederzeit fehlschlagen. Bei der Verwendung dieser API sollten Sie sicherstellen, dass Sie Kontrollen implementiert haben, um diese Probleme abzufangen, bevor sie eine Produktionsumgebung erreichen. Sie könnten beispielsweise automatisierte Tests zum Theme/Plugin hinzufügen oder eine Staging-Umgebung nutzen, um eingehende Discourse-Updates gegen Ihr Theme/Plugin zu testen.
Grundlegende Verwendung
api.modifyClass kann verwendet werden, um die Funktionen und Eigenschaften jeder Klasse zu ändern, die über den Ember-Resolver zugänglich ist. Dazu gehören Routen, Controller, Services und Komponenten von Discourse.
modifyClass nimmt zwei Argumente entgegen:
-
resolverName(string) - Konstruieren Sie dies, indem Sie den Typ (z. B.component/controller/etc.) gefolgt von einem Doppelpunkt und dem (dasherisierten) Dateinamen der Klasse verwenden. Zum Beispiel:component:d-button,component:modal/login,controller:user,route:applicationusw. -
callback(function) - eine Funktion, die die vorhandene Klassendefinition empfängt und dann eine erweiterte Version zurückgibt.
Zum Beispiel, um die click()-Aktion auf d-button zu ändern:
api.modifyClass(
"component:d-button",
(Superclass) =>
class extends Superclass {
@action
click() {
console.log("button was clicked");
super.click();
}
}
);
Die class extends ...-Syntax spiegelt die von JS-Kindklassen wider. Im Allgemeinen kann jede Syntax/Funktion, die von Kindklassen unterstützt wird, hier angewendet werden. Dazu gehören super, statische Eigenschaften/Funktionen und mehr.
Es gibt jedoch einige Einschränkungen. Das modifyClass-System erkennt nur Änderungen am JavaScript-prototype der Klasse. Praktisch bedeutet dies:
-
Das Einführen oder Ändern eines
constructor()wird nicht unterstütztapi.modifyClass( "component:foo", (Superclass) => class extends Superclass { constructor() { // Dies wird nicht unterstützt. Der Konstruktor wird ignoriert } } ); -
Das Einführen oder Ändern von Klassenfeldern wird nicht unterstützt (obwohl einige dekorierte Klassenfelder wie
@trackedverwendet werden können)api.modifyClass( "component:foo", (Superclass) => class extends Superclass { someField = "foo"; // NICHT UNTERSTÜTZT - nicht kopieren @tracked someOtherField = "foo"; // Dies ist in Ordnung } ); -
Einfache Klassenfelder in der ursprünglichen Implementierung können in keiner Weise überschrieben werden (obwohl, wie oben erwähnt,
@tracked-Felder durch ein anderes@tracked-Feld überschrieben werden können)// Kerncode: class Foo extends Component { // Dieses Kernfeld kann nicht überschrieben werden someField = "original"; // Dieses Kern-Tracked-Feld kann überschrieben werden, indem // `@tracked someTrackedField =` im modifyClass-Aufruf enthalten ist @tracked someTrackedField = "original"; }
Wenn Sie feststellen, dass Sie diese Dinge tun möchten, dann ist Ihr Anwendungsfall möglicherweise besser durch das Einreichen eines PRs zur Einführung neuer APIs im Kern (z. B. plugin outlets, transformers oder spezielle APIs) abgedeckt.
Aufrüsten von Legacy-Syntax
In der Vergangenheit wurde modifyClass mit einer Objekt-Literal-Syntax wie dieser aufgerufen:
// Veraltete Syntax - nicht verwenden
api.modifyClass("component:some-component", {
someFunction() {
const original = this._super();
return original + " some change";
},
pluginId: "some-unique-id",
});
Diese Syntax wird nicht mehr empfohlen und hat bekannte Fehler (z. B. Überschreiben von Gettern oder @actions). Jeglicher Code, der diese Syntax verwendet, sollte auf die oben beschriebene native Klassen-Syntax aktualisiert werden. Im Allgemeinen kann die Konvertierung erfolgen durch:
- Entfernen von
pluginId- dies ist nicht mehr erforderlich - Aktualisieren auf die moderne native Klassen-Syntax, wie oben beschrieben
- Testen Sie Ihre Änderungen
Fehlerbehebung
Klasse bereits initialisiert
Wenn Sie modifyClass in einem Initializer verwenden, sehen Sie möglicherweise diese Warnung in der Konsole:
Attempted to modify "{name}", but it was already initialized earlier in the boot process
In der Theme/Plugin-Entwicklung wird dieser Fehler normalerweise auf zwei Arten eingeführt:
-
Hinzufügen eines
lookup()verursachte den FehlerWenn Sie einen Singleton zu früh im Boot-Prozess
lookup()en, führt dies dazu, dass späteremodifyClass-Aufrufe fehlschlagen. In dieser Situation sollten Sie versuchen, den Lookup so zu verschieben, dass er später erfolgt. Zum Beispiel würden Sie etwas wie dieses ändern:// Service im Initializer nachschlagen, dann zur Laufzeit verwenden (schlecht!) export default apiInitializer("0.8", (api) => { const composerService = api.container.lookup("service:composer"); api.composerBeforeSave(async () => { composerService.doSomething(); }); });Dazu:
// 'Just in time' Lookup des Services (gut!) export default apiInitializer("0.8", (api) => { api.composerBeforeSave(async () => { const composerService = api.container.lookup("service:composer"); composerService.doSomething(); }); }); -
Hinzufügen eines neuen
modifyClassverursachte den FehlerWenn der Fehler durch das Hinzufügen eines
modifyClass-Aufrufs durch Ihr Theme/Plugin verursacht wird, müssen Sie ihn früher im Boot-Prozess verschieben. Dies geschieht häufig beim Überschreiben von Methoden auf Services (z. B.topicTrackingState) und auf Modellen, die früh im App-Boot-Prozess initialisiert werden (z. B. wird einmodel:userfürservice:current-userinitialisiert).Das Verschieben des
modifyClass-Aufrufs früher im Boot-Prozess bedeutet normalerweise, den Aufruf in einenpre-initializerzu verschieben und ihn so zu konfigurieren, dass er vor dem ‘inject-discourse-objects’-Initialisierer von Discourse ausgeführt wird. Zum Beispiel:// (plugin)/assets/javascripts/discourse/pre-initializers/extend-user-for-my-plugin.js // oder // (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", { myNewUserFunction() { return "hello world"; }, }); }, initialize() { withPluginApi("0.12.1", this.initializeWithApi); }, };Diese Modifikation des Benutzer-Modells sollte nun ohne Warnung funktionieren, und die neue Methode wird auf dem
currentUser-Objekt verfügbar sein.
Dieses Dokument ist versionskontrolliert - schlagen Sie Änderungen auf github vor.