ModifyClass zur Änderung des Kernverhaltens verwenden

Für fortgeschrittene Themes und Plugins bietet Discourse das modifyClass-System. Dies ermöglicht es Ihnen, Funktionalitäten in vielen der JavaScript-Klassen des Kerns zu erweitern und zu überschreiben.

Wann sollte modifyClass verwendet werden

modifyClass sollte die letzte Möglichkeit sein, wenn Ihre Anpassung nicht über die stabileren APIs zur Anpassung von Discourse vorgenommen werden kann (z. B. Plugin-API-Methoden, Plugin-Outlets, Transformer).

Der Core-Code kann sich jederzeit ändern. Daher können durch modifyClass vorgenommene Anpassungen 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. Zum Beispiel könnten Sie automatisierte Tests für das 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 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) – Erstellen Sie diesen, indem Sie den Typ (z. B. component/controller/etc.), gefolgt von einem Doppelpunkt, gefolgt vom (mit Bindestrichen versehenen) Dateinamen der Klasse verwenden. Zum Beispiel: component:d-button, component:modal/login, controller:user, route:application usw.

  • callback (Funktion) – Eine Funktion, die die vorhandene Klassendefinition erhält und dann eine erweiterte Version zurückgibt.

Zum Beispiel, um die click()-Aktion auf 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-Unterklassen nach. Im Allgemeinen können alle Syntax/Funktionen, die von Unterklassen unterstützt werden, 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ützt

    api.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 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)

    // Core Code:
    class Foo extends Component {
      // Dieses Core-Feld kann nicht überschrieben werden
      someField = "original";
    
      // Dieses Core-Tracked-Feld kann durch 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 erfüllt, indem Sie einen PR erstellen, um neue APIs im Core 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 folgt 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. das Überschreiben von Gettern oder @actions). Jeglicher Code, der diese Syntax verwendet, sollte auf die oben beschriebene native Klassensyntax aktualisiert werden. Im Allgemeinen kann die Konvertierung wie folgt erfolgen:

  1. pluginId entfernen – dies ist nicht mehr erforderlich
  2. Auf die moderne native Klassensyntax aktualisieren, die oben beschrieben wurde
  3. Ihre Änderungen testen

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 (Versucht, “{Name}” zu modifizieren, aber es wurde früher im Boot-Prozess bereits initialisiert)

In der Theme-/Plugin-Entwicklung wird dieser Fehler normalerweise auf zwei Arten eingeführt:

  • Das Hinzufügen eines lookup() verursachte den Fehler

    Wenn Sie einen Singleton zu früh im Boot-Prozess lookup()en, führt dies dazu, dass alle späteren modifyClass-Aufrufe fehlschlagen. In dieser Situation sollten Sie versuchen, das Lookup zu einem späteren Zeitpunkt zu verschieben. Zum Beispiel würden Sie etwas wie folgt ändern:

    // Service im Initializer nachschlagen und dann zur Laufzeit verwenden (schlecht!)
    export default apiInitializer((api) => {
      const composerService = api.container.lookup("service:composer");
      api.composerBeforeSave(async () => {
        composerService.doSomething();
      });
    });
    

    Zu diesem:

    // 'Just in time' Nachschlagen des Services (gut!)
    export default apiInitializer((api) => {
      api.composerBeforeSave(async () => {
        const composerService = api.container.lookup("service:composer");
        composerService.doSomething();
      });
    });
    
  • Das Hinzufügen eines neuen modifyClass verursachte den Fehler

    Wenn der Fehler durch den Theme-/Plugin-Aufruf von modifyClass eingeführt wird, müssen Sie ihn früher im Boot-Prozess verschieben. Dies geschieht häufig, wenn Methoden auf Services überschrieben werden (z. B. topicTrackingState) und auf Modellen, die früh im App-Boot-Prozess initialisiert werden (z. B. ein model:user wird für service:current-user initialisiert).

    Das Verschieben des modifyClass-Aufrufs früher in den Boot-Prozess bedeutet normalerweise, den Aufruf in einen pre-initializer zu verschieben und ihn so zu konfigurieren, dass er vor dem Initializer von Discourse 'inject-discourse-objects' läuft. 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 verwaltet – Änderungen vorschlagen auf github.

16 „Gefällt mir“