Para temas e plugins avançados, o Discourse oferece o sistema modifyClass. Ele permite estender e sobrescrever a funcionalidade de muitas das classes JavaScript do núcleo.
Quando usar modifyClass
modifyClass deve ser uma última opção, quando a personalização não puder ser feita por meio das APIs de personalização mais estáveis do Discourse (por exemplo, métodos de plugin-api, plugin outlets, transformers).
O código do núcleo pode mudar a qualquer momento. Portanto, personalizações feitas via modifyClass podem quebrar a qualquer momento. Ao usar esta API, você deve garantir que tenha controles em vigor para capturar esses problemas antes que eles atinjam um site em produção. Por exemplo, você pode adicionar testes automatizados ao tema/plugin ou usar um site de staging para testar atualizações do Discourse contra seu tema/plugin.
Uso Básico
api.modifyClass pode ser usado para modificar as funções e propriedades de qualquer classe acessível via resolver do Ember. Isso inclui rotas, controladores, serviços e componentes do Discourse.
modifyClass aceita dois argumentos:
-
resolverName(string) - construa isso usando o tipo (por exemplo, component/controller/etc.), seguido por dois-pontos, seguido pelo nome do arquivo (dasherized) da classe. Por exemplo:component:d-button,component:modal/login,controller:user,route:application, etc. -
callback(function) - uma função que recebe a definição da classe existente e retorna uma versão estendida.
Por exemplo, para modificar a ação click() no d-button:
api.modifyClass(
"component:d-button",
(Superclass) =>
class extends Superclass {
@action
click() {
console.log("button was clicked");
super.click();
}
}
);
A sintaxe class extends ... imita a de classes filhas em JS. Em geral, qualquer sintaxe/recursos suportados por classes filhas podem ser aplicados aqui. Isso inclui super, propriedades/funções estáticas, entre outros.
No entanto, há algumas limitações. O sistema modifyClass só detecta alterações no prototype JS da classe. Na prática, isso significa:
-
introduzir ou modificar um
constructor()não é suportadoapi.modifyClass( "component:foo", (Superclass) => class extends Superclass { constructor() { // This is not supported. The constructor will be ignored } } ); -
introduzir ou modificar campos de classe não é suportado (embora alguns campos de classe decorados, como
@tracked, possam ser usados)api.modifyClass( "component:foo", (Superclass) => class extends Superclass { someField = "foo"; // NOT SUPPORTED - do not copy @tracked someOtherField = "foo"; // This is ok } ); -
campos de classe simples na implementação original não podem ser sobrescritos de nenhuma forma (embora, como acima, campos
@trackedpossam ser sobrescritos por outro 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"; }
Se você se deparar com a necessidade de fazer essas coisas, seu caso de uso pode ser melhor atendido fazendo um PR para introduzir novas APIs no núcleo (por exemplo, plugin outlets, transformers, ou APIs específicas).
Atualizando Sintaxe Legada
No passado, modifyClass era chamado usando uma sintaxe 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 sintaxe não é mais recomendada e possui bugs conhecidos (por exemplo, sobrescrever getters ou @actions). Qualquer código que use esta sintaxe deve ser atualizado para usar a sintaxe de classe nativa descrita acima. Em geral, a conversão pode ser feita por:
- removendo
pluginId- isso não é mais necessário - Atualizando para a sintaxe moderna de classe nativa descrita acima
- Testando suas alterações
Solução de Problemas
Classe já inicializada
Ao usar modifyClass em um initializer, você pode ver este aviso no console:
Attempted to modify "{name}", but it was already initialized earlier in the boot process
No desenvolvimento de temas/plugins, existem duas maneiras comuns pelas quais esse erro é introduzido:
-
Adicionar um
lookup()causou o erroSe você fizer um
lookup()de um singleton muito cedo no processo de inicialização, isso causará a falha de qualquer chamada posterior demodifyClass. Nessa situação, você deve tentar mover o lookup para ocorrer mais tarde. Por exemplo, você mudaria algo como este:// 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(); }); });Para este:
// 'Just in time' lookup of service (good!) export default apiInitializer((api) => { api.composerBeforeSave(async () => { const composerService = api.container.lookup("service:composer"); composerService.doSomething(); }); }); -
Adicionar um novo
modifyClasscausou o erroSe o erro for introduzido pelo seu tema/plugin adicionando uma chamada
modifyClass, você precisará movê-la para mais cedo no processo de inicialização. Isso geralmente acontece ao sobrescrever métodos em serviços (por exemplo, topicTrackingState) e em modelos que são inicializados cedo no processo de inicialização do aplicativo (por exemplo, ummodel:useré inicializado paraservice:current-user).Mover a chamada modifyClass para mais cedo no processo de inicialização normalmente significa mover a chamada para um
pre-initializer, e configurá-lo para executar antes do initializer ‘inject-discourse-objects’ do Discourse. Por exemplo:// (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 modificação do modelo de usuário deve agora funcionar sem imprimir um aviso, e o novo método estará disponível no objeto currentUser.
Este documento é controlado por versão - sugira alterações no github.