Para temas y plugins avanzados, Discourse ofrece el sistema modifyClass. Esto permite extender y sobrescribir la funcionalidad de muchas de las clases de JavaScript del núcleo.
Cuándo usar modifyClass
modifyClass debe ser un último recurso, cuando la personalización no pueda realizarse mediante las APIs de personalización más estables de Discourse (p. ej., 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 mediante modifyClass podrían romperse en cualquier momento. Al usar esta API, debe asegurarse de tener controles en su lugar para detectar esos problemas antes de que lleguen a un sitio de producción. Por ejemplo, podría agregar pruebas automatizadas al tema/plugin, o podría usar un sitio de pruebas (staging) para probar las actualizaciones entrantes de Discourse contra su 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 resolver de Ember. Esto incluye las rutas, controladores, servicios y componentes de Discourse.
modifyClass toma dos argumentos:
-
resolverName(cadena) - constrúyalo utilizando el tipo (p. ej., component/controller/etc.), seguido de dos puntos, seguido del nombre de archivo (en formato guion) de la clase. Por ejemplo:component:d-button,component:modal/login,controller:user,route:application, etc. -
callback(función) - 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 la de las clases hijas de JS. En general, cualquier sintaxis/funcionalidad admitida por las clases hijas puede aplicarse 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 JS de la clase. En la práctica, esto significa:
-
introducir o modificar un
constructor()no está admitidoapi.modifyClass( "component:foo", (Superclass) => class extends Superclass { constructor() { // This is not supported. The constructor will be ignored } } ); -
introducir o modificar campos de clase no está admitido (aunque algunos campos de clase decorados, como
@tracked, pueden usarse)api.modifyClass( "component:foo", (Superclass) => class extends Superclass { someField = "foo"; // NOT SUPPORTED - do not copy @tracked someOtherField = "foo"; // This is ok } ); -
los campos de clase simples en la implementación original no pueden sobrescribirse de ninguna manera (aunque, como se mencionó anteriormente, los campos
@trackedpueden sobrescribirse por otro 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"; }
Si se encuentra queriendo hacer estas cosas, es posible que su caso de uso se satisfaga mejor creando una PR para introducir nuevas APIs en el núcleo (p. ej., plugin outlets, transformers o APIs específicas).
Actualización de la sintaxis legada
En el pasado, modifyClass se llamaba utilizando una sintaxis de literal de objeto como esta:
// Outdated syntax - do not use
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 (p. ej., sobrescribir getters o @actions). Cualquier código que utilice esta sintaxis debe actualizarse para usar la sintaxis de clase nativa descrita anteriormente. En general, la conversión se puede realizar de la siguiente manera:
- eliminar
pluginId- ya no es necesario - Actualice a la sintaxis moderna de clase nativa descrita anteriormente
- Pruebe sus cambios
Solución de problemas
Clase ya inicializada
Al usar modifyClass en un inicializador, es posible que vea 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 normalmente se introduce este error:
-
Agregar un
lookup()causó el errorSi realiza un
lookup()de un singleton demasiado temprano en el proceso de arranque, hará que cualquier llamada posterior amodifyClassfalle. En esta situación, debe intentar mover el lookup para que ocurra más tarde. Por ejemplo, cambiaría algo como esto:// 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 esto:
// 'Just in time' lookup of service (good!) export default apiInitializer((api) => { api.composerBeforeSave(async () => { const composerService = api.container.lookup("service:composer"); composerService.doSomething(); }); }); -
Agregar un nuevo
modifyClasscausó el errorSi el error se introduce porque su tema/plugin agrega una llamada a
modifyClass, deberá moverla más temprano en el proceso de arranque. Esto suele ocurrir al sobrescribir métodos en servicios (p. ej., topicTrackingState) y en modelos que se inicializan temprano en el proceso de arranque de la aplicación (p. ej., unmodel:userse inicializa paraservice:current-user).Mover la llamada modifyClass más temprano en el proceso de arranque normalmente significa mover la llamada a un
pre-initializer, y configurarla 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 // 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); }, };Esta modificación del modelo de usuario debería funcionar ahora sin imprimir una advertencia, y el nuevo método estará disponible en el objeto currentUser.
Este documento está bajo control de versiones - sugiera cambios en github.