Para temas y complementos 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 API de personalización más estables de Discourse (por ejemplo, métodos de plugin-api, plugin outlets, transformers).
El código del núcleo puede cambiar en cualquier momento. Por lo tanto, las personalizaciones realizadas a través de modifyClass podrían 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/complemento, o podrías usar un sitio de staging para probar las actualizaciones entrantes de Discourse contra tu tema/complemento.
Uso básico
api.modifyClass se puede usar para modificar las funciones y propiedades de cualquier clase que sea accesible a través del resolutor de Ember. Esto incluye las rutas, controladores, servicios y componentes de Discourse.
modifyClass toma dos argumentos:
-
resolverName(string) - constrúyelo usando el tipo (por ejemplo,component/controller/etc.), seguido de dos puntos, seguido del nombre del archivo (en formato guionizado) 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("button was clicked");
super.click();
}
}
);
La sintaxis class extends ... imita la de las clases hijas de JS. En general, cualquier sintaxis/característica compatible con las clases hijas se puede aplicar aquí. Esto incluye super, propiedades/funciones estáticas y más.
Sin embargo, existen algunas limitaciones. El sistema modifyClass solo detecta cambios en el prototype de JS de la clase. En la práctica, eso significa:
-
la introducción o modificación de un
constructor()no es compatibleapi.modifyClass( "component:foo", (Superclass) => class extends Superclass { constructor() { // Esto no es compatible. El constructor será ignorado } } ); -
la introducción o modificación de 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 del núcleo: class Foo extends Component { // Este campo del núcleo no se puede anular someField = "original"; // Este campo rastreado del núcleo 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 estar mejor satisfecho haciendo un PR para introducir nuevas API en el núcleo (por ejemplo, plugin outlets, transformers, o API personalizadas).
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 (por ejemplo, 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 hacer:
- eliminando
pluginId- ya no es necesario - Actualizando a la sintaxis moderna de clase nativa descrita anteriormente
- Probando tus cambios
Solución de problemas
Clase ya inicializada
Al usar modifyClass en un inicializador, es posible que veas esta advertencia en la consola:
Attempted to modify "{name}", but it was already initialized earlier in the boot process
En el desarrollo de temas/complementos, hay dos formas en que este error se introduce normalmente:
-
Agregar un
lookup()causó el errorSi haces
lookup()a 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:// Buscar servicio en el inicializador, luego usarlo 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 de servicio 'Just in time' (¡bien!) export default apiInitializer("0.8", (api) => { api.composerBeforeSave(async () => { const composerService = api.container.lookup("service:composer"); composerService.doSomething(); }); }); -
Agregar un nuevo
modifyClasscausó el errorSi el error es introducido por tu tema/complemento al agregar una llamada
modifyClass, entonces necesitarás moverla más temprano en el proceso de arranque. Esto ocurre comúnmente al anular métodos en servicios (por ejemplo,topicTrackingState), y en modelos que se inicializan temprano en el proceso de arranque de la aplicación (por ejemplo, unmodel:userse inicializa paraservice:current-user).Mover la llamada
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", { myNewUserFunction() { return "hello world"; }, }); }, initialize() { withPluginApi("0.12.1", 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.