Para temas y plugins avanzados, Discourse ofrece el sistema modifyClass. Esto te permite extender y anular la funcionalidad en muchas de las clases de javascript principales.
Cuándo usar modifyClass
modifyClass debe ser el último recurso, cuando tu personalización no se puede realizar a través de las API de personalización más estables de Discourse (por ejemplo, métodos de plugin-api, plugin outlets, transformers).
El código principal puede cambiar en cualquier momento. Y por lo tanto, las personalizaciones realizadas a través de modifyClass pueden romperse en cualquier momento. Al usar esta API, debes asegurarte de tener controles implementados para detectar esos problemas antes de que lleguen a un sitio de producción. Por ejemplo, podrías agregar pruebas automatizadas al tema/plugin, o podrías usar un sitio de staging para probar las actualizaciones entrantes de Discourse contra tu tema/plugin.
Uso Básico
api.modifyClass se puede usar para modificar las funciones y propiedades de cualquier clase que sea accesible a través del resolvedor de Ember. Esto incluye las rutas, controladores, servicios y componentes de Discourse.
modifyClass toma dos argumentos:
-
resolverName(string) - constrúyelo usando el tipo (ej.component/controller/etc.), seguido de dos puntos, seguido del nombre (guionizado) del archivo de la clase. Por ejemplo:component:d-button,component:modal/login,controller:user,route:application, etc. -
callback(function) - una función que recibe la definición de la clase existente y luego devuelve una versión extendida.
Por ejemplo, para modificar la acción click() en d-button:
api.modifyClass(
"component:d-button",
(Superclass) =>
class extends Superclass {
@action
click() {
console.log("botón fue clickeado");
super.click();
}
}
);
La sintaxis class extends ... imita a las clases hijo de JS. En general, cualquier sintaxis/característica soportada por las clases hijo se puede aplicar aquí. Esto incluye super, propiedades/funciones estáticas, y más.
Sin embargo, hay algunas limitaciones. El sistema modifyClass solo detecta cambios en el prototype de JS de la clase. Prácticamente, eso significa:
-
introducir o modificar un
constructor()no es compatibleapi.modifyClass( "component:foo", (Superclass) => class extends Superclass { constructor() { // Esto no es compatible. El constructor será ignorado } } ); -
introducir o modificar campos de clase no es compatible (aunque algunos campos de clase decorados, como
@tracked, se pueden usar)api.modifyClass( "component:foo", (Superclass) => class extends Superclass { someField = "foo"; // NO SOPORTADO - no copiar @tracked someOtherField = "foo"; // Esto está bien } ); -
los campos de clase simples en la implementación original no se pueden anular de ninguna manera (aunque, como se mencionó anteriormente, los campos
@trackedse pueden anular con otro campo@tracked)// Código principal: class Foo extends Component { // Este campo principal no se puede anular someField = "original"; // Este campo tracked principal se puede anular incluyendo // `@tracked someTrackedField =` en la llamada a modifyClass @tracked someTrackedField = "original"; }
Si te encuentras queriendo hacer estas cosas, entonces tu caso de uso podría satisfacerse mejor haciendo un PR para introducir nuevas API en el núcleo (ej. plugin outlets, transformers, o API a medida).
Actualización de Sintaxis Heredada
En el pasado, modifyClass se llamaba usando una sintaxis de literal de objeto como esta:
// Sintaxis desactualizada - no usar
api.modifyClass("component:some-component", {
someFunction() {
const original = this._super();
return original + " algún cambio";
}
pluginId: "some-unique-id"
});
Esta sintaxis ya no se recomienda y tiene errores conocidos (ej. anulación de getters o @actions). Cualquier código que use esta sintaxis debe actualizarse para usar la sintaxis de clase nativa descrita anteriormente. En general, la conversión se puede realizar mediante:
- eliminar
pluginId- esto ya no es necesario - Actualizar a la sintaxis moderna de clase nativa descrita anteriormente
- Probar tus cambios
Solución de Problemas
Clase ya inicializada
Al usar modifyClass en un inicializador, es posible que veas esta advertencia en la consola:
Intentó modificar "{name}", pero ya fue inicializado antes en el proceso de arranque
En el desarrollo de temas/plugins, hay dos formas en que este error se introduce normalmente:
-
Añadir un
lookup()causó el errorSi haces
lookup()de un singleton demasiado pronto en el proceso de arranque, causará que cualquier llamada posterior amodifyClassfalle. En esta situación, deberías intentar mover el lookup para que ocurra más tarde. Por ejemplo, cambiarías algo como esto:// Busca el servicio en el inicializador, luego úsalo en tiempo de ejecución (¡malo!) export default apiInitializer((api) => { const composerService = api.container.lookup("service:composer"); api.composerBeforeSave(async () => { composerService.doSomething(); }); });A esto:
// *Lookup* del servicio "Just in time" (¡bueno!) export default apiInitializer((api) => { api.composerBeforeSave(async () => { const composerService = api.container.lookup("service:composer"); composerService.doSomething(); }); }); -
Añadir un nuevo
modifyClasscausó el errorSi el error es introducido por tu tema/plugin al agregar una llamada a
modifyClass, entonces necesitarás moverla más temprano en el proceso de arranque. Esto sucede comúnmente cuando se anulan métodos en servicios (ej.topicTrackingState), y en modelos que se inicializan temprano en el arranque de la aplicación (ej. unmodel:userse inicializa paraservice:current-user).Mover la llamada a
modifyClassmás temprano en el proceso de arranque normalmente significa mover la llamada a unpre-initializer, y configurarlo para que se ejecute antes del inicializador ‘inject-discourse-objects’ de Discourse. Por ejemplo:// (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", (Superclass) => class extends Superclass { myNewUserFunction() { return "hello world"; }, }); }, initialize() { withPluginApi(this.initializeWithApi); }, };Esta modificación del modelo de usuario ahora debería funcionar sin imprimir una advertencia, y el nuevo método estará disponible en el objeto
currentUser.
Este documento está controlado por versiones - sugiere cambios en github.