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 del núcleo.
Cuándo usar modifyClass
modifyClass debe ser el último recurso, cuando tu personalización no se puede realizar a través de las APIs de personalización más estables de Discourse (por ejemplo, métodos plugin-api, plugin outlets, transformers).
El código del núcleo 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 añadir 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 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 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("button was clicked");
super.click();
}
}
);
La sintaxis class extends ... imita a las clases hijas de JS. En general, cualquier sintaxis/característica soportada por las clases hijas 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, esto significa:
-
introducir o modificar un
constructor()no es soportadoapi.modifyClass( "component:foo", (Superclass) => class extends Superclass { constructor() { // Esto no es soportado. El constructor será ignorado } } ); -
introducir o modificar campos de clase no es soportado (aunque algunos campos de clase decorados, como
@trackedpueden ser usados)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 pueden ser anulados de ninguna manera (aunque, como se mencionó anteriormente, los campos
@trackedpueden ser anulados incluyendo@tracked someTrackedField =en la llamada amodifyClass)// Código del núcleo: class Foo extends Component { // Este campo del núcleo no puede ser anulado someField = "original"; // Este campo tracked del núcleo puede ser anulado 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 APIs en el núcleo (ej. plugin outlets, transformers, o APIs a medida).
Actualización de Sintaxis Heredada
En el pasado, modifyClass se llamaba usando una sintaxis de literal de objeto como esta:
// Sintaxis obsoleta - no usar
api.modifyClass("component:some-component", {
someFunction() {
const original = this._super();
return original + " some change";
}
pluginId: "some-unique-id"
});
Esta sintaxis ya no se recomienda, y tiene errores conocidos (ej. anular 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 hacer:
- eliminando
pluginId- esto ya no es requerido - 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, podrías ver esta advertencia en la consola:
Attempted to modify "{name}", but it was already initialized earlier in the boot process
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 ellookuppara 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 (¡mal!) export default apiInitializer("0.8", (api) => { const composerService = api.container.lookup("service:composer"); api.composerBeforeSave(async () => { composerService.doSomething(); }); });A esto:
// Búsqueda 'Just in time' del servicio (¡bien!) export default apiInitializer("0.8", (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 añadiendo una llamada a
modifyClass, entonces necesitarás moverla antes 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
modifyClassantes en el proceso de arranque normalmente significa mover la llamada a unpre-initializer, y configurarlo para que se ejecute antes del inicializador de ‘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.