Für fortgeschrittene Themes und Plugins bietet Discourse das modifyClass-System. Damit können Sie Funktionalitäten in vielen der JavaScript-Klassen des Kerns erweitern und überschreiben.
Wann modifyClass verwendet werden sollte
modifyClass sollte nur als letzte Option genutzt werden, wenn Ihre Anpassung nicht über die stabileren Anpassungs-APIs von Discourse (z. B. plugin-api-Methoden, plugin outlets, transformers) vorgenommen werden kann.
Der Code des Kerns kann sich jederzeit ändern. Daher können Anpassungen, die über modifyClass vorgenommen werden, jederzeit fehlerhaft werden. Wenn Sie diese API verwenden, sollten Sie sicherstellen, dass Sie Mechanismen implementiert haben, um solche Probleme zu erkennen, bevor sie auf einer Produktivumgebung auftreten. Zum Beispiel könnten Sie automatisierte Tests für das Theme/das Plugin hinzufügen oder eine Staging-Umgebung nutzen, um eingehende Discourse-Updates gegen Ihr Theme/Ihr Plugin zu testen.
Grundlegende Verwendung
api.modifyClass kann verwendet werden, um Funktionen und Eigenschaften jeder Klasse zu ändern, die über den Ember-Resolver zugänglich ist. Dazu gehören die Routen, Controller, Dienste und Komponenten von Discourse.
modifyClass erwartet zwei Argumente:
-
resolverName(Zeichenkette) – konstruieren Sie dies, indem Sie den Typ (z. B. component/controller/etc.) verwenden, gefolgt von einem Doppelpunkt und dem (dasherisierten) Dateinamen der Klasse. 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 click()-Aktion von 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 ... imitiert die von JS-Unterklassen. Im Allgemeinen können hier alle Syntaxelemente/Funktionen angewendet werden, die von Unterklassen unterstützt 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 das:
-
Das Einführen oder Ändern eines
constructor()wird nicht unterstütztapi.modifyClass( "component:foo", (Superclass) => class extends Superclass { constructor() { // This is not supported. The constructor will be ignored } } ); -
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"; // NOT SUPPORTED - do not copy @tracked someOtherField = "foo"; // This is ok } ); -
Einfache Klassenfelder in der ursprünglichen Implementierung können auf keine Weise überschrieben werden (obwohl, wie oben erwähnt,
@tracked-Felder durch ein anderes@tracked-Feld überschrieben werden können)// Core code: class Foo extends Component { // This core field cannot be overridden someField = "original"; // This core tracked field can be overridden by including // `@tracked someTrackedField =` in the modifyClass call @tracked someTrackedField = "original"; }
Wenn Sie feststellen, dass Sie diese Dinge tun möchten, könnte Ihr Anwendungsfall besser durch das Erstellen eines Pull Requests abgedeckt werden, um neue APIs im Kern einzuführen (z. B. plugin outlets, transformers oder maßgeschneiderte APIs).
Aufrüstung der Legacy-Syntax
In der Vergangenheit wurde modifyClass mit einer Objekt-Literal-Syntax wie folgt aufgerufen:
// Outdated syntax - do not use
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. das Überschreiben von Gettern oder @actions). Jeder Code, der diese Syntax verwendet, sollte so aktualisiert werden, dass die oben beschriebene Native-Class-Syntax verwendet wird. Im Allgemeinen kann die Konvertierung wie folgt erfolgen:
- Entfernen von
pluginId– dies ist nicht mehr erforderlich - Aktualisierung auf die oben beschriebene moderne Native-Class-Syntax
- Testen Ihrer Ä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 tritt dieser Fehler normalerweise auf zwei Arten auf:
-
Hinzufügen einer
lookup()hat den Fehler verursachtWenn Sie ein 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 später auszuführen. Zum Beispiel würden Sie etwas wie Folgendes ändern:// Lookup service in initializer, then use it at runtime (bad!) export default apiInitializer((api) => { const composerService = api.container.lookup("service:composer"); api.composerBeforeSave(async () => { composerService.doSomething(); }); });zu Folgendem:
// 'Just in time' lookup of service (good!) export default apiInitializer((api) => { api.composerBeforeSave(async () => { const composerService = api.container.lookup("service:composer"); composerService.doSomething(); }); }); -
Hinzufügen eines neuen
modifyClasshat den Fehler verursachtWenn der Fehler durch das Hinzufügen eines
modifyClass-Aufrufs durch Ihr Theme/Ihr Plugin verursacht wird, müssen Sie diesen früher im Boot-Prozess verschieben. Dies passiert häufig, wenn Methoden auf Diensten (z. B. topicTrackingState) und auf Modellen überschrieben werden, die früh im Boot-Prozess der App 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 einen
pre-initializerzu verschieben und ihn so zu konfigurieren, dass er vor dem Discourse-Initializer ‘inject-discourse-objects’ ausgeführt wird. Zum Beispiel:// (plugin)/assets/javascripts/discourse/pre-initializers/extend-user-for-my-plugin.js // or // (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 Änderung des User-Modells sollte jetzt ohne Ausgabe einer Warnung funktionieren, und die neue Methode wird auf dem currentUser-Objekt verfügbar sein.
Dieses Dokument wird versioniert verwaltet – schlagen Sie Änderungen auf github vor.