Per temi e plugin avanzati, Discourse offre il sistema modifyClass. Questo ti permette di estendere e sovrascrivere la funzionalità di molte classi JavaScript principali.
Quando usare modifyClass
modifyClass dovrebbe essere l’ultima risorsa, quando la tua personalizzazione non può essere effettuata tramite le API di personalizzazione più stabili di Discourse (ad es. metodi plugin-api, plugin outlets, transformers).
Il codice principale può cambiare in qualsiasi momento. Pertanto, le personalizzazioni effettuate tramite modifyClass potrebbero interrompersi in qualsiasi momento. Quando usi questa API, dovresti assicurarti di avere controlli in atto per catturare questi problemi prima che raggiungano un sito di produzione. Ad esempio, potresti aggiungere test automatici al tema/plugin, oppure potresti usare un sito di staging per testare gli aggiornamenti in arrivo di Discourse rispetto al tuo tema/plugin.
Utilizzo di base
api.modifyClass può essere utilizzato per modificare le funzioni e le proprietà di qualsiasi classe accessibile tramite il risolutore Ember. Ciò include route, controller, servizi e componenti di Discourse.
modifyClass accetta due argomenti:
-
resolverName(stringa) - costruiscilo usando il tipo (ad es.component/controller/ecc.), seguito da due punti, seguito dal nome del file (dasherizzato) della classe. 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 quindi restituisce una versione estesa.
Ad esempio, per modificare l’azione click() su 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/funzionalità supportata dalle classi figlie può essere applicata qui. Ciò 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, ciò significa:
-
l’introduzione o la modifica di un
constructor()non è supportataapi.modifyClass( "component:foo", (Superclass) => class extends Superclass { constructor() { // Questo non è supportato. Il costruttore verrà ignorato } } ); -
l’introduzione o la modifica di 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"; // NON SUPPORTATO - non copiare @tracked someOtherField = "foo"; // Questo va bene } ); -
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)// Codice principale: class Foo extends Component { // Questo campo principale non può essere sovrascritto someField = "original"; // Questo campo tracciato principale può essere sovrascritto includendo // `@tracked someTrackedField =` nella chiamata modifyClass @tracked someTrackedField = "original"; }
Se ti trovi a voler fare queste cose, allora il tuo caso d’uso potrebbe essere meglio soddisfatto creando una PR per introdurre nuove API nel codice principale (ad es. plugin outlets, transformers, o API su misura).
Aggiornamento della sintassi legacy
In passato, modifyClass veniva chiamato utilizzando una sintassi a oggetto letterale come questa:
// Sintassi obsoleta - non usare
api.modifyClass("component:some-component", {
someFunction() {
const original = this._super();
return original + " some change";
},
pluginId: "some-unique-id",
});
Questa sintassi non è più raccomandata e presenta bug noti (ad es. sovrascrittura di getter o @actions). Qualsiasi codice che utilizza questa sintassi dovrebbe essere aggiornato per utilizzare la sintassi nativa di classe descritta sopra. In generale, la conversione può essere effettuata tramite:
- rimuovere
pluginId- questo non è più richiesto - Aggiornare alla sintassi nativa di classe moderna descritta sopra
- Testare le tue modifiche
Risoluzione dei problemi
Classe già inizializzata
Quando si utilizza modifyClass in un inizializzatore, potresti vedere 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 un
lookup()ha causato l’erroreSe esegui
lookup()di un singleton troppo presto nel processo di avvio, ciò causerà il fallimento di eventuali chiamatemodifyClasssuccessive. In questa situazione, dovresti provare a spostare il lookup in modo che avvenga più tardi. Ad esempio, cambieresti qualcosa del genere:// Lookup del servizio nell'inizializzatore, quindi usalo al runtime (male!) export default apiInitializer("0.8", (api) => { const composerService = api.container.lookup("service:composer"); api.composerBeforeSave(async () => { composerService.doSomething(); }); });A questo:
// Lookup del servizio 'Just in time' (bene!) export default apiInitializer("0.8", (api) => { api.composerBeforeSave(async () => { const composerService = api.container.lookup("service:composer"); composerService.doSomething(); }); }); -
L’aggiunta di un nuovo
modifyClassha causato l’erroreSe l’errore è introdotto dalla chiamata
modifyClassaggiunta dal tuo tema/plugin, dovrai spostarla prima nel processo di avvio. Questo accade comunemente quando si sovrascrivono metodi sui servizi (ad es.topicTrackingState), e sui modelli che vengono inizializzati presto nel processo di avvio dell’app (ad es. unmodel:userviene inizializzato perservice:current-user).Spostare la chiamata
modifyClassprima nel processo di avvio significa normalmente spostare la chiamata a unpre-initializere configurarlo per essere eseguito prima dell’inizializzatore ‘inject-discourse-objects’ di Discourse. Ad esempio:// (plugin)/assets/javascripts/discourse/pre-initializers/extend-user-for-my-plugin.js // o // (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", { myNewUserFunction() { return "hello world"; }, }); }, initialize() { withPluginApi("0.12.1", this.initializeWithApi); }, };Questa modifica del modello utente dovrebbe ora funzionare senza stampare un avviso, e il nuovo metodo sarà disponibile sull’oggetto
currentUser.
Questo documento è controllato dalla versione - suggerisci modifiche su github.