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 die letzte Möglichkeit sein, wenn Ihre Anpassung nicht über stabilere APIs zur Anpassung von Discourse möglich ist (z. B. plugin-api-Methoden, Plugin-Outlets, Transformer).
Der Kerncode kann sich jederzeit ändern. Daher können Anpassungen, die über modifyClass vorgenommen wurden, jederzeit fehlschlagen. Wenn Sie diese API verwenden, sollten Sie sicherstellen, dass Sie Vorkehrungen getroffen haben, um solche Probleme abzufangen, bevor sie eine Produktionsseite erreichen. Sie könnten zum Beispiel 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 modifizieren, 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 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 bestehende Klassendefinition erhält und dann eine erweiterte Version zurückgibt.
Zum Beispiel, um die Aktion click() bei d-button zu modifizieren:
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 JavaScript-prototype der Klasse. Praktisch bedeutet dies:
-
Das Einführen oder Modifizieren 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 Modifizieren 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 diese Dinge tun möchten, wird Ihr Anwendungsfall möglicherweise besser durch das Einreichen eines PR erfüllt, 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 umgestellt werden. Im Allgemeinen kann die Konvertierung erfolgen durch:
- Entfernen von
pluginId– dies ist nicht mehr erforderlich - Umstellung auf die moderne native Klassensyntax, die oben beschrieben wurde
- Testen Ihrer Änderungen
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 verursacht:
-
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 später durchzuführen. Zum Beispiel würden Sie etwas wie dies ändern:// Service im Initialisierer nachschlagen und ihn 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
modifyClassverursachte den FehlerWenn der Fehler durch das Hinzufügen eines
modifyClass-Aufrufs durch Ihr Theme/Plugin verursacht wird, müssen Sie es 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 Verschieben des
modifyClass-Aufrufs früher 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 – schlagen Sie Änderungen auf github vor.