Per i temi e i plugin avanzati, Discourse offre il sistema modifyClass. Questo consente di estendere e sovrascrivere la funzionalità di molte delle classi JavaScript del core.
Quando utilizzare modifyClass
modifyClass dovrebbe essere l’ultima risorsa, quando la personalizzazione non può essere effettuata tramite le API di personalizzazione più stabili di Discourse (ad es. metodi plugin-api, plugin outlets, transformer).
Il codice del core può cambiare in qualsiasi momento. Di conseguenza, le personalizzazioni effettuate tramite modifyClass potrebbero andare in errore in qualsiasi momento. Quando si utilizza questa API, è necessario assicurarsi di avere dei controlli in grado di rilevare tali problemi prima che raggiungano un sito in produzione. Ad esempio, è possibile aggiungere test automatizzati al tema/plugin oppure utilizzare un sito di staging per testare gli aggiornamenti in ingresso di Discourse contro il proprio tema/plugin.
Uso di base
api.modifyClass può essere utilizzato per modificare le funzioni e le proprietà di qualsiasi classe accessibile tramite il resolver di Ember. Questo include le route, i controller, i servizi e i componenti di Discourse.
modifyClass accetta due argomenti:
-
resolverName(stringa) - costruiscilo utilizzando il tipo (ad es. component/controller/etc.), seguito da due punti, seguito dal nome del file della classe (in formato dasherized). Ad esempio:component:d-button,component:modal/login,controller:user,route:application, ecc. -
callback(funzione) - una funzione che riceve la definizione della classe esistente e restituisce quindi una versione estesa.
Ad esempio, per modificare l’azione click() di d-button:
api.modifyClass(
"component:d-button",
(Superclass) =>
class extends Superclass {
@action
click() {
console.log("button was clicked");
super.click();
}
}
);
La sintassi class extends ... imita quella delle classi figlie JS. In generale, qualsiasi sintassi/feature supportata dalle classi figlie può essere applicata qui. Questo include super, proprietà/funzioni statiche e altro ancora.
Tuttavia, ci sono alcune limitazioni. Il sistema modifyClass rileva solo le modifiche al prototype JS della classe. In pratica, questo significa:
-
l’introduzione o la modifica di un
constructor()non è supportataapi.modifyClass( "component:foo", (Superclass) => class extends Superclass { constructor() { // This is not supported. The constructor will be ignored } } ); -
l’introduzione o la modifica dei campi di classe non è supportata (sebbene alcuni campi di classe decorati, come
@tracked, possano essere utilizzati)api.modifyClass( "component:foo", (Superclass) => class extends Superclass { someField = "foo"; // NOT SUPPORTED - do not copy @tracked someOtherField = "foo"; // This is ok } ); -
i semplici campi di classe nell’implementazione originale non possono essere sovrascritti in alcun modo (sebbene, come sopra, i campi
@trackedpossano essere sovrascritti da un altro campo@tracked)// 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"; }
Se ti trovi a voler fare queste cose, il tuo caso d’uso potrebbe essere meglio soddisfatto creando una PR per introdurre nuove API nel core (ad es. plugin outlets, transformer o API specifiche).
Aggiornamento della sintassi legacy
In passato, modifyClass veniva chiamato utilizzando una sintassi a letterale di oggetto come questa:
// Outdated syntax - do not use
api.modifyClass("component:some-component", {
someFunction() {
const original = this._super();
return original + " some change";
}
pluginId: "some-unique-id"
});
Questa sintassi non è più consigliata e presenta bug noti (ad es. sovrascrittura di getter o @actions). Qualsiasi codice che utilizza questa sintassi dovrebbe essere aggiornato per utilizzare la sintassi di classe nativa descritta sopra. In generale, la conversione può essere effettuata:
- rimuovendo
pluginId- non è più necessario - aggiornando alla moderna sintassi di classe nativa descritta sopra
- testando le modifiche
Risoluzione dei problemi
Classe già inizializzata
Quando si utilizza modifyClass in un initializer, è possibile visualizzare questo avviso nella console:
Attempted to modify "{name}", but it was already initialized earlier in the boot process
Nello sviluppo di temi/plugin, ci sono due modi in cui questo errore viene normalmente introdotto:
-
L’aggiunta di una
lookup()ha causato l’erroreSe si esegue
lookup()di un singleton troppo presto nel processo di avvio, ciò causerà il fallimento di qualsiasi successiva chiamata amodifyClass. In questa situazione, si dovrebbe cercare di spostare la lookup in un momento successivo. Ad esempio, si passerebbe da qualcosa come questo:// 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(); }); });a questo:
// 'Just in time' lookup of service (good!) export default apiInitializer((api) => { api.composerBeforeSave(async () => { const composerService = api.container.lookup("service:composer"); composerService.doSomething(); }); }); -
L’aggiunta di una nuova
modifyClassha causato l’erroreSe l’errore viene introdotto dal fatto che il tuo tema/plugin aggiunge una chiamata a
modifyClass, dovrai spostarla più in alto nel processo di avvio. Questo accade comunemente quando si sovrascrivono metodi su servizi (ad es. topicTrackingState) e su modelli che vengono inizializzati precocemente nel processo di avvio dell’app (ad es. unmodel:userviene inizializzato perservice:current-user).Spostare la chiamata modifyClass più in alto nel processo di avvio significa normalmente spostare la chiamata in un
pre-initializere configurarla per essere eseguita prima dell’initializer ‘inject-discourse-objects’ di Discourse. Ad esempio:// (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); }, };Questa modifica del modello user dovrebbe ora funzionare senza stampare un avviso e il nuovo metodo sarà disponibile sull’oggetto currentUser.
Questo documento è sotto controllo di versione - suggerisci modifiche su github.