고급 테마와 플러그인의 경우, Discourse는 modifyClass 시스템을 제공합니다. 이를 통해 코어 JavaScript 클래스의 많은 기능을 확장하고 오버라이드할 수 있습니다.
modifyClass를 사용할 때
modifyClass는 Discourse의 보다 안정적인 커스터마이징 API(예: plugin-api 메서드, plugin outlets, transformers)를 통해 커스터마이징을 할 수 없는 경우를 제외하고는 최후의 수단으로 사용해야 합니다.
코어의 코드는 언제든지 변경될 수 있습니다. 따라서 modifyClass를 통해 만든 커스터마이징도 언제든지 깨질 수 있습니다. 이 API를 사용할 때는 프로덕션 사이트에 문제가 도달하기 전에 이를 포착할 수 있는 제어가 이루어지도록 해야 합니다. 예를 들어, 테마/플러그인에 자동화된 테스트를 추가하거나, 스테이징 사이트를 사용하여 Discourse 업데이트를 테마/플러그인에 대해 테스트할 수 있습니다.
기본 사용법
api.modifyClass는 Ember 리졸버를 통해 접근할 수 있는 모든 클래스의 함수와 프로퍼티를 수정하는 데 사용할 수 있습니다. 여기에는 Discourse의 라우트, 컨트롤러, 서비스 및 컴포넌트가 포함됩니다.
modifyClass는 두 개의 인수를 받습니다:
-
resolverName(문자열) - 유형(예: component/controller 등)을 사용하여 구성한 후 콜론을 붙이고, 클래스의 (대시화된) 파일 이름 이름을 따릅니다. 예를 들어:component:d-button,component:modal/login,controller:user,route:application등. -
callback(함수) - 기존 클래스 정의를 받아 확장된 버전을 반환하는 함수.
예를 들어, d-button의 click() 액션을 수정하려면:
api.modifyClass(
"component:d-button",
(Superclass) =>
class extends Superclass {
@action
click() {
console.log("button was clicked");
super.click();
}
}
);
class extends ... 구문은 JS 자식 클래스의 구문을 모방합니다. 일반적으로 자식 클래스에서 지원되는 모든 구문/기능을 여기에 적용할 수 있습니다. 여기에는 super, 정적 프로퍼티/함수 등이 포함됩니다.
그러나 몇 가지 제한 사항이 있습니다. modifyClass 시스템은 클래스의 JS prototype에 대한 변경 사항만 감지합니다. 실질적으로 이는 다음과 같은 것을 의미합니다:
-
constructor()를 도입하거나 수정하는 것은 지원되지 않습니다api.modifyClass( "component:foo", (Superclass) => class extends Superclass { constructor() { // This is not supported. The constructor will be ignored } } ); -
클래스 필드를 도입하거나 수정하는 것은 지원되지 않습니다(다만
@tracked와 같이 데코레이터가 적용된 클래스 필드는 사용할 수 있음)api.modifyClass( "component:foo", (Superclass) => class extends Superclass { someField = "foo"; // NOT SUPPORTED - do not copy @tracked someOtherField = "foo"; // This is ok } ); -
원래 구현의 단순한 클래스 필드는 어떤 방식으로든 오버라이드할 수 없습니다(위와 마찬가지로,
@tracked필드는 다른@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"; }
이런 것들을 하고 싶다면, 코어에 새로운 API(예: plugin outlets, transformers, 또는 전용 API)를 도입하는 PR을 만드는 것이 사용 사례를 더 잘 충족시킬 수 있습니다.
레거시 구문 업그레이드
과거에는 modifyClass가 다음과 같은 객체 리터럴 구문을 사용하여 호출되었습니다:
// Outdated syntax - do not use
api.modifyClass("component:some-component", {
someFunction() {
const original = this._super();
return original + " some change";
}
pluginId: "some-unique-id"
});
이 구문은 더 이상 권장되지 않으며, 알려진 버그(예: getter 또는 @actions 오버라이드)가 있습니다. 이 구문을 사용하는 모든 코드는 위에서 설명한 네이티브 클래스 구문으로 업데이트되어야 합니다. 일반적으로 변환은 다음과 같이 수행할 수 있습니다:
pluginId제거 - 더 이상 필요하지 않습니다- 위에서 설명한 최신 네이티브 클래스 구문으로 업데이트
- 변경 사항 테스트
문제 해결
클래스가 이미 초기화됨
initializer에서 modifyClass를 사용할 때, 콘솔에 다음과 같은 경고가 표시될 수 있습니다:
Attempted to modify "{name}", but it was already initialized earlier in the boot process
테마/플러그인 개발에서 이 오류가 일반적으로 도입되는 두 가지 방식이 있습니다:
-
lookup()을 추가하여 오류가 발생함부트 프로세스에서 너무 일찍 싱글턴을
lookup()하면, 이후의modifyClass호출이 실패하게 됩니다. 이 상황에서는 lookup을 나중에 수행하도록 이동해 보십시오. 예를 들어, 다음과 같은 것을:// 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(); }); });이렇게 변경합니다:
// 'Just in time' lookup of service (good!) export default apiInitializer((api) => { api.composerBeforeSave(async () => { const composerService = api.container.lookup("service:composer"); composerService.doSomething(); }); }); -
새로운
modifyClass를 추가하여 오류가 발생함테마/플러그인이
modifyClass호출을 추가하여 오류가 도입된 경우, 부트 프로세스에서 더 앞쪽으로 이동해야 합니다. 이는 서비스(예: topicTrackingState)의 메서드를 오버라이드하거나, 앱 부트 프로세스에서 초기화되는 모델(예:service:current-user에 대해 초기화되는model:user)의 경우 흔히 발생합니다.부트 프로세스에서 modifyClass 호출을 더 앞쪽으로 이동하는 것은 일반적으로 호출을
pre-initializer로 이동하고, Discourse의 ‘inject-discourse-objects’ initializer보다 먼저 실행되도록 구성하는 것을 의미합니다. 예를 들어:// (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); }, };이 사용자 모델 수정은 이제 경고를 출력하지 않고 작동해야 하며, 새로운 메서드는 currentUser 객체에 사용 가능하게 됩니다.
이 문서는 버전 관리됩니다 - 변경 사항을 github에서 제안하십시오.