高度なテーマやプラグインの場合、Discourse は modifyClass システムを提供します。これにより、コアの多くの JavaScript クラスの機能を拡張およびオーバーライドできます。
modifyClass を使用するタイミング
modifyClass は、Discourse のより安定したカスタマイズ API (例: plugin-api メソッド、プラグインアウトレット、トランスフォーマー) を介してカスタマイズが行えない場合の最終手段として使用する必要があります。
コアのコードはいつでも変更される可能性があります。したがって、modifyClass を介して行われたカスタマイズは、いつでも壊れる可能性があります。この API を使用する場合は、本番サイトに問題が到達する前にそれらの問題を検出するための制御が配置されていることを確認する必要があります。たとえば、テーマ/プラグインに自動テストを追加したり、ステージングサイトを使用して、テーマ/プラグインに対する受信した Discourse の更新をテストしたりできます。
基本的な使い方
api.modifyClass は、Ember リゾルバを介してアクセス可能な任意のクラスの関数とプロパティを変更するために使用できます。これには、Discourse のルート、コントローラー、サービス、およびコンポーネントが含まれます。
modifyClass は 2 つの引数を取ります。
-
resolverName(文字列) - タイプ (例:component/controller/etc.)、コロン、クラスの (ダッシュ化された) ファイル名で構成します。たとえば、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"; // これは OK です } ); -
元の実装の単純なクラスフィールドは、いかなる方法でもオーバーライドできません (ただし、上記のように、
@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呼び出しが失敗します。この場合、ルックアップを後で発生するように移動してみてください。たとえば、次のようなものを変更します。// 初期化子でサービスをルックアップし、実行時に使用する (悪い!) export default apiInitializer("0.8", (api) => { const composerService = api.container.lookup("service:composer"); api.composerBeforeSave(async () => { composerService.doSomething(); }); });次のようにします。
// サービスの「ジャストインタイム」ルックアップ (良い!) export default apiInitializer("0.8", (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", { myNewUserFunction() { return "hello world"; }, }); }, initialize() { withPluginApi("0.12.1", this.initializeWithApi); }, };これで、ユーザーモデルのこの変更は警告なしで機能し、新しいメソッドは
currentUserオブジェクトで利用可能になります。
このドキュメントはバージョン管理されています - 変更は github で提案してください。