高度なテーマやプラグインには、Discourse が modifyClass システムを提供しています。これにより、コアの JavaScript クラスの多くで機能を拡張・上書きできます。
modifyClass を使用するタイミング
modifyClass は最終手段として使用するべきです。Discourse のより安定したカスタマイズ API(例: プラグイン API メソッド、プラグインアウトレット、トランスフォーマー)ではカスタマイズできない場合に限ります。
コアのコードはいつでも変更される可能性があります。そのため、modifyClass を通じたカスタマイズは、いつでも壊れる可能性があります。この API を使用する際は、本番環境に問題が到達する前にそれらを捕捉するための制御を講じておく必要があります。例えば、テーマ/プラグインに自動テストを追加したり、ステージングサイトで Discourse の更新をテーマ/プラグインに対してテストしたりすることができます。
基本的な使用方法
api.modifyClass は、Ember リゾルバー経由でアクセス可能な任意のクラスの関数やプロパティを変更するために使用できます。これには、Discourse のルート、コントローラー、サービス、コンポーネントが含まれます。
modifyClass は2つの引数を取ります:
-
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() { // これはサポートされていません。コンストラクターは無視されます } } ); -
クラスフィールドの導入または変更はサポートされていません(ただし、
@trackedのような装飾されたクラスフィールドは使用できます)api.modifyClass( "component:foo", (Superclass) => class extends Superclass { someField = "foo"; // サポートされていません - コピーしないでください @tracked someOtherField = "foo"; // これは問題ありません } ); -
元の実装における単純なクラスフィールドは、いかなる方法でも上書きできません(ただし、上記と同様に、
@trackedフィールドは別の@trackedフィールドによって上書きできます)// コアコード: class Foo extends Component { // このコアフィールドは上書きできません someField = "original"; // このコアのトラッキングフィールドは、modifyClass 呼び出しに // `@tracked someTrackedField =` を含めることで上書きできます @tracked someTrackedField = "original"; }
これらのことをやりたいと考える場合は、コアに新しい API(例: プラグインアウトレット、トランスフォーマー、または独自の API)を導入するための PR を作成することで、より適切に要件を満たせる可能性があります。
レガシー構文からのアップグレード
過去、modifyClass は以下のオブジェクトリテラル構文を使用して呼び出されていました:
// 古い構文 - 使用しないでください
api.modifyClass("component:some-component", {
someFunction() {
const original = this._super();
return original + " some change";
}
pluginId: "some-unique-id"
});
この構文は推奨されておらず、既知のバグ(例: ゲッターや @actions の上書き)があります。この構文を使用しているコードは、上記で説明したネイティブクラス構文に更新する必要があります。一般的に、変換は以下のように行われます:
pluginIdの削除 - これはもはや必要ありません- 上記で説明したモダンなネイティブクラス構文への更新
- 変更のテスト
トラブルシューティング
クラスがすでに初期化されています
イニシャライザー内で modifyClass を使用する場合、コンソールにこの警告が表示されることがあります:
Attempted to modify "{name}", but it was already initialized earlier in the boot process
テーマ/プラグインの開発において、このエラーが発生する通常の理由は2つあります:
-
lookup()の追加がエラーの原因ブートプロセスの初期段階でシングルトンを
lookup()すると、その後のmodifyClass呼び出しが失敗する原因になります。この状況では、lookup を後に行うように変更する必要があります。例えば、以下のようなものを:// イニシャライザー内でサービスを lookup し、ランタイムで使用する(悪い例!) export default apiInitializer((api) => { const composerService = api.container.lookup("service:composer"); api.composerBeforeSave(async () => { composerService.doSomething(); }); });以下のように変更します:
// サービスの「ジャストインタイム」lookup(良い例!) 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’ イニシャライザーより前に実行されるように設定することを意味します。例えば:// (plugin)/assets/javascripts/discourse/pre-initializers/extend-user-for-my-plugin.js // または // (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 で。