Für erweiterte Themes und Plugins bietet Discourse das modifyClass-System. Dies ermöglicht es Ihnen, die Funktionalität vieler Kern-JavaScript-Klassen zu erweitern und zu überschreiben.
Wann modifyClass verwendet werden sollte
modifyClass sollte das letzte Mittel sein, wenn Ihre Anpassung nicht über stabilere APIs zur Anpassung von Discourse vorgenommen werden kann (z. B. plugin-api-Methoden, Plugin-Outlets, Transformer).
Der Kerncode kann sich jederzeit ändern. Daher können Anpassungen, die über modifyClass vorgenommen werden, jederzeit fehlschlagen. Wenn Sie diese API verwenden, sollten Sie sicherstellen, dass Sie Vorkehrungen getroffen haben, um diese Probleme abzufangen, bevor sie auf einer Produktionsseite auftreten. Sie könnten beispielsweise automatisierte Tests für das Theme/Plugin hinzufügen oder eine Staging-Umgebung verwenden, 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 akzeptiert zwei Argumente:
-
resolverName(String) – Konstruieren Sie dies, indem Sie den Typ (z. B.component/controller/etc.) gefolgt von einem Doppelpunkt und dann dem (mit Bindestrichen versehenen) Dateinamen der Klasse verwenden. Zum Beispiel:component:d-button,component:modal/login,controller:user,route:applicationusw. -
callback(Funktion) – eine Funktion, die die vorhandene Klassendefinition erhält und dann eine erweiterte Version zurückgibt.
Zum Beispiel, um die Aktion click() bei d-button zu ändern:
api.modifyClass(
"component:d-button",
(Superclass) =>
class extends Superclass {
@action
click() {
console.log("button was clicked");
super.click();
}
}
);
Die Syntax class extends ... ahmt die von JS-Kindklassen nach. 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 JS 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
@tracked, verwendet 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 durch die Aufnahme von // `@tracked someTrackedField =` im modifyClass-Aufruf überschrieben werden @tracked someTrackedField = "original"; }
Wenn Sie feststellen, dass Sie dies tun möchten, wird Ihr Anwendungsfall möglicherweise besser erfüllt, indem Sie einen PR erstellen, um neue APIs im Kern einzuführen (z. B. Plugin-Outlets, Transformer oder maßgeschneiderte APIs).
Veraltete Syntax aktualisieren
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 weist bekannte Fehler auf (z. B. das Überschreiben von Gettern oder @actions). Jeder Code, der diese Syntax verwendet, sollte auf die oben beschriebene native Klassensyntax aktualisiert werden. Im Allgemeinen kann die Konvertierung wie folgt erfolgen:
pluginIdentfernen – dies ist nicht mehr erforderlich- Auf die moderne native Klassensyntax aktualisieren, die oben beschrieben wurde
- Ihre Änderungen testen
Fehlerbehebung
Klasse bereits initialisiert
Wenn Sie modifyClass in einem Initialisierer verwenden, sehen Sie möglicherweise diese Warnung in der Konsole:
Attempted to modify "{name}", but it was already initialized earlier in the boot process
Bei der Theme-/Plugin-Entwicklung wird dieser Fehler normalerweise auf zwei Arten eingeführt:
-
Das Hinzufügen eines
lookup()verursachte den FehlerWenn Sie ein Singleton zu früh im Boot-Prozess
lookup()en, führt dies dazu, dass alle späterenmodifyClass-Aufrufe fehlschlagen. In diesem Fall sollten Sie versuchen, das Lookup nach hinten zu verschieben. Sie würden beispielsweise etwas wie dies ändern:// Service im Initialisierer nachschlagen und dann zur Laufzeit verwenden (schlecht!) export default apiInitializer("0.8", (api) => { const composerService = api.container.lookup("service:composer"); api.composerBeforeSave(async () => { composerService.doSomething(); }); });Zu diesem:
// '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(); }); }); -
Das Hinzufügen eines neuen
modifyClass-Aufrufs verursachte 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, wenn Methoden auf Services (z. B.topicTrackingState) und auf Modellen überschrieben werden, die früh im App-Boot-Prozess initialisiert werden (z. B. einmodel:userwird fürservice:current-userinitialisiert).Das frühere Verschieben des
modifyClass-Aufrufs in den Boot-Prozess bedeutet normalerweise, den Aufruf in einenpre-initializerzu verschieben und ihn so zu konfigurieren, dass er vor dem Initialisierer von Discourse ‘inject-discourse-objects’ 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", (Superclass) => class extends Superclass { myNewUserFunction() { return "hello world"; }, }); }, initialize() { withPluginApi(this.initializeWithApi); }, };Diese Modifikation des Benutzermodells sollte nun ohne Warnung funktionieren, und die neue Methode wird auf dem
currentUser-Objekt verfügbar sein.
Dieses Dokument wird versioniert – Änderungen auf github vorschlagen.