jointrashposs/content/blog/2023-12-19-mac202319.md
かっこかり 811461f235
New Crowdin updates (#98)
* New translations thread-mute.md (Chinese Simplified)

* New translations webhook.md (Chinese Simplified)

* New translations 1.troubleshooting.md (Chinese Simplified)

* New translations donate.md (Chinese Simplified)

* New translations misskey-hub.md (Chinese Simplified)

* New translations announcement.md (Chinese Simplified)

* New translations role.md (Chinese Simplified)

* New translations 1.index.md (Chinese Simplified)

* New translations bash.md (Chinese Simplified)

* New translations docker.md (Chinese Simplified)

* New translations kubernetes.md (Chinese Simplified)

* New translations manual.md (Chinese Simplified)

* New translations ubuntu-manual.md (Chinese Simplified)

* New translations cdn.md (Chinese Simplified)

* New translations disable-timelines.md (Chinese Simplified)

* New translations 1.index.md (Chinese Simplified)

* New translations permission.md (Chinese Simplified)

* New translations 1.index.md (Chinese Simplified)

* New translations 2.miauth.md (Chinese Simplified)

* New translations 5.releases.md (Chinese Simplified)

* New translations 1.index.md (Chinese Simplified)

* New translations 3.oauth.md (Chinese Simplified)

* New translations ja-jp.yml (Chinese Traditional)

* New translations 1.about-misskey.md (Chinese Traditional)

* New translations 2.get-started.md (Chinese Traditional)

* New translations 3.join-server.md (Chinese Traditional)

* New translations 4.things-to-know.md (Chinese Traditional)

* New translations ads.md (Chinese Traditional)

* New translations drive.md (Chinese Traditional)

* New translations mfm.md (Chinese Traditional)

* New translations mute-and-block.md (Chinese Traditional)

* New translations pages.md (Chinese Traditional)

* New translations poll.md (Chinese Traditional)

* New translations share-form.md (Chinese Traditional)

* New translations thread-mute.md (Chinese Traditional)

* New translations webhook.md (Chinese Traditional)

* New translations 1.troubleshooting.md (Chinese Traditional)

* New translations donate.md (Chinese Traditional)

* New translations misskey-hub.md (Chinese Traditional)

* New translations announcement.md (Chinese Traditional)

* New translations role.md (Chinese Traditional)

* New translations 1.index.md (Chinese Traditional)

* New translations bash.md (Chinese Traditional)

* New translations docker.md (Chinese Traditional)

* New translations kubernetes.md (Chinese Traditional)

* New translations manual.md (Chinese Traditional)

* New translations ubuntu-manual.md (Chinese Traditional)

* New translations cdn.md (Chinese Traditional)

* New translations disable-timelines.md (Chinese Traditional)

* New translations 1.index.md (Chinese Traditional)

* New translations permission.md (Chinese Traditional)

* New translations 1.index.md (Chinese Traditional)

* New translations 2.miauth.md (Chinese Traditional)

* New translations publish-on-your-website.md (Chinese Traditional)

* New translations 5.releases.md (Chinese Traditional)

* New translations 1.index.md (Chinese Traditional)

* New translations 3.oauth.md (Chinese Traditional)

* New translations 1.about-misskey.md (English)

* New translations 2.get-started.md (English)

* New translations 3.join-server.md (English)

* New translations 4.things-to-know.md (English)

* New translations ads.md (English)

* New translations drive.md (English)

* New translations mfm.md (English)

* New translations mute-and-block.md (English)

* New translations pages.md (English)

* New translations poll.md (English)

* New translations share-form.md (English)

* New translations thread-mute.md (English)

* New translations webhook.md (English)

* New translations 1.troubleshooting.md (English)

* New translations donate.md (English)

* New translations misskey-hub.md (English)

* New translations announcement.md (English)

* New translations role.md (English)

* New translations 1.index.md (English)

* New translations bash.md (English)

* New translations docker.md (English)

* New translations kubernetes.md (English)

* New translations manual.md (English)

* New translations ubuntu-manual.md (English)

* New translations cdn.md (English)

* New translations disable-timelines.md (English)

* New translations 1.index.md (English)

* New translations permission.md (English)

* New translations 1.index.md (English)

* New translations 2.miauth.md (English)

* New translations 5.releases.md (English)

* New translations 1.index.md (English)

* New translations 3.oauth.md (English)

* New translations 1.about-misskey.md (Indonesian)

* New translations 2.get-started.md (Indonesian)

* New translations 3.join-server.md (Indonesian)

* New translations 4.things-to-know.md (Indonesian)

* New translations ads.md (Indonesian)

* New translations drive.md (Indonesian)

* New translations mfm.md (Indonesian)

* New translations mute-and-block.md (Indonesian)

* New translations pages.md (Indonesian)

* New translations poll.md (Indonesian)

* New translations share-form.md (Indonesian)

* New translations thread-mute.md (Indonesian)

* New translations webhook.md (Indonesian)

* New translations 1.troubleshooting.md (Indonesian)

* New translations donate.md (Indonesian)

* New translations misskey-hub.md (Indonesian)

* New translations announcement.md (Indonesian)

* New translations role.md (Indonesian)

* New translations 1.index.md (Indonesian)

* New translations bash.md (Indonesian)

* New translations docker.md (Indonesian)

* New translations kubernetes.md (Indonesian)

* New translations manual.md (Indonesian)

* New translations ubuntu-manual.md (Indonesian)

* New translations cdn.md (Indonesian)

* New translations disable-timelines.md (Indonesian)

* New translations 1.index.md (Indonesian)

* New translations permission.md (Indonesian)

* New translations 1.index.md (Indonesian)

* New translations 2.miauth.md (Indonesian)

* New translations 5.releases.md (Indonesian)

* New translations 1.index.md (Indonesian)

* New translations 3.oauth.md (Indonesian)

* New translations 5.releases.md (Korean)

* New translations ja-jp.yml (English)

* New translations docker.md (English)

* New translations ja-jp.yml (Indonesian)

* New translations 5.releases.md (Korean)

* New translations ja-jp.yml (Indonesian)

* New translations 1.about-misskey.md (Indonesian)

* New translations 1.index.md (Indonesian)

* New translations ads.md (Indonesian)

* New translations antenna.md (Indonesian)

* New translations charts.md (Indonesian)

* New translations clip.md (Indonesian)

* New translations custom-emoji.md (Indonesian)

* New translations deck.md (Indonesian)

* New translations drive.md (Indonesian)

* New translations favorite.md (Indonesian)

* New translations follow.md (Indonesian)

* New translations hashtag.md (Indonesian)

* New translations mention.md (Indonesian)

* New translations mfm.md (Indonesian)

* New translations 1.index.md (Indonesian)

* New translations announcement.md (Indonesian)

* New translations bash.md (Indonesian)

* New translations ja-jp.yml (Indonesian)

* New translations mute-and-block.md (Indonesian)

* New translations pages.md (Indonesian)

* New translations apps.md (Indonesian)

* New translations donate.md (Indonesian)

* New translations bash.md (Indonesian)

* New translations docker.md (Indonesian)

* New translations manual.md (Indonesian)

* New translations 1.about-misskey.md (Chinese Traditional)

* New translations 1.about-misskey.md (Chinese Traditional)

* New translations create-plugin.md (French)

* New translations create-plugin.md (Italian)

* New translations create-plugin.md (Polish)

* New translations create-plugin.md (Chinese Simplified)

* New translations create-plugin.md (Chinese Traditional)

* New translations create-plugin.md (Indonesian)

* New translations aiscript.md (French)

* New translations aiscript.md (Italian)

* New translations aiscript.md (Korean)

* New translations aiscript.md (Polish)

* New translations aiscript.md (Chinese Simplified)

* New translations aiscript.md (Chinese Traditional)

* New translations aiscript.md (English)

* New translations aiscript.md (Indonesian)

* New translations ja-jp.yml (Chinese Traditional)

* New translations ja-jp.yml (Chinese Traditional)

* New translations troubleshooting.md (French)

* New translations libraries.md (French)

* New translations troubleshooting.md (Italian)

* New translations libraries.md (Italian)

* New translations troubleshooting.md (Korean)

* New translations libraries.md (Korean)

* New translations troubleshooting.md (Polish)

* New translations libraries.md (Polish)

* New translations troubleshooting.md (Chinese Simplified)

* New translations libraries.md (Chinese Simplified)

* New translations troubleshooting.md (Chinese Traditional)

* New translations libraries.md (Chinese Traditional)

* New translations troubleshooting.md (English)

* New translations libraries.md (English)

* New translations troubleshooting.md (Indonesian)

* New translations libraries.md (Indonesian)

* New translations ja-jp.yml (Chinese Traditional)

* New translations ja-jp.yml (French)

* New translations 2.miauth.md (French)

* New translations ja-jp.yml (Italian)

* New translations 2.miauth.md (Italian)

* New translations ja-jp.yml (Korean)

* New translations ja-jp.yml (Polish)

* New translations 2.miauth.md (Polish)

* New translations ja-jp.yml (Chinese Simplified)

* New translations 2.miauth.md (Chinese Simplified)

* New translations 2.miauth.md (Chinese Traditional)

* New translations ja-jp.yml (English)

* New translations 2.miauth.md (English)

* New translations ja-jp.yml (Indonesian)

* New translations 2.miauth.md (Indonesian)

* Revert "New translations 1.about-misskey.md (English)"

This reverts commit 2b9572c2cf.

* Revert "New translations 2.get-started.md (English)"

This reverts commit 2fa05a5aee.

* Revert "New translations 4.things-to-know.md (English)"

This reverts commit 698c5e980d.

* Revert "New translations ads.md (English)"

This reverts commit f0e1cd2965.

* Revert "New translations mfm.md (English)"

This reverts commit 51dcc0fc10.

* Revert "New translations webhook.md (English)"

This reverts commit cfbebc5783.

* Revert "New translations 1.index.md (English)"

This reverts commit 67d2bec45c.

* Revert "New translations disable-timelines.md (English)"

This reverts commit 19ead90df6.

* Revert "New translations webhook.md (French)"

This reverts commit 52e6a8429a.

* Revert "New translations webhook.md (Chinese Traditional)"

This reverts commit 395f0fbc4a.

* Revert "New translations webhook.md (Polish)"

This reverts commit ac4e2c534e.

* Revert "New translations webhook.md (Korean)"

This reverts commit 14853e1eab.

* Revert "New translations pages.md (Korean)"

This reverts commit f88e73d1ce.

* Revert "New translations bash.md (Korean)"

This reverts commit 60c9fae6a8.

* Revert "New translations kubernetes.md (Korean)"

This reverts commit 2239e8fd23.

* Revert "New translations ubuntu-manual.md (Korean)"

This reverts commit 3aab982625.

* Revert "New translations 1.about-misskey.md (Chinese Traditional)"

This reverts commit 5ac92102db.

* Revert "New translations 2.get-started.md (Chinese Traditional)"

This reverts commit d090253e34.

* Revert "New translations 3.join-server.md (Chinese Traditional)"

This reverts commit 634db4d49b.

* Revert "New translations 4.things-to-know.md (Chinese Traditional)"

This reverts commit 383ee08013.

* Revert "New translations ads.md (Chinese Traditional)"

This reverts commit 6ed3b6885f.

* Revert "New translations drive.md (Chinese Traditional)"

This reverts commit 988cacb17e.

* Revert "New translations mfm.md (Chinese Traditional)"

This reverts commit 6b53e36fc4.

* Revert "New translations mute-and-block.md (Chinese Traditional)"

This reverts commit 44b1b3c723.

* Revert "New translations pages.md (Chinese Traditional)"

This reverts commit 4e60747a55.

* Revert "New translations poll.md (Chinese Traditional)"

This reverts commit 00385dc6a1.

* Revert "New translations share-form.md (Chinese Traditional)"

This reverts commit 8858eaf2f7.

* Revert "New translations thread-mute.md (Chinese Traditional)"

This reverts commit d07bd70000.

* Revert "New translations 1.troubleshooting.md (Chinese Traditional)"

This reverts commit 7405a91ff9.

* fix

* New translations aiscript.md (Korean)

* New translations ja-jp.yml (Chinese Traditional)

* New translations troubleshooting.md (Korean)

* New translations ja-jp.yml (French)

* New translations ja-jp.yml (Italian)

* New translations ja-jp.yml (Korean)

* New translations ja-jp.yml (Polish)

* New translations ja-jp.yml (Chinese Simplified)

* New translations ja-jp.yml (English)

* New translations ja-jp.yml (Indonesian)

* New translations 5.releases.md (Korean)

* fix

* fix

* fix

* fix
2024-01-23 13:18:36 +09:00

19 KiB
Raw Blame History

date
2023-12-19

Deep dive on the linkage between Misskey and Vue.js

:::tip

これは Misskey Advent Calendar 202319日目の記事です.

:::

こんにちは, コアチームメンバーの acid-chicken です.Misskeyの開発にはnighthike v4あたりから参加しており, 現在は本業の傍ら, 余暇にリファクタリングやコードレビューなどをやっていることが多いです.

Misskeyでは2018年からフロントエンドのUIフレームワークにVue.jsを採用しており, メジャーアップデートのマイグレーションなどを経て, 現在も継続して使用しています. 今回は,Misskeyのフロントエンド構造について,Vue.jsの機能との接点を中心に深掘りしていきます.

:::tip

大まかな解説は既にsyuilo連載「Misskey & Webテクロジー最前線」9月などで触れられています. 一方で, 本記事では連載で触れないような, 細かい部分に焦点を絞った話題を扱うため, もしかすると読んでいてつまらない内容になっているかもしれません. 予めご了承ください.

:::

Misskeyのフロントエンド構造

現在 (nasubi開始時点)Misskeyは, 以下のようなレイヤー構造の構成によってフロントエンドを描画しています.

::X__Blog__2023-12-19-mac202319__Figure1 ::

Misskeyのフロントエンド構造

コードベースでは, 図における上部のレイヤーと下部のレイヤーが分かれており,(少なくとも便宜上は) 前者をフロントエンド, 後者をバックエンドと呼んでいます. ビルド時に,フロントエンドはViteによってバンドルされ, その成果物はバックエンドのアセットとして配置されます. バックエンドは, ユーザーエージェント (多くの場合,Webブラウザ)からのリクエストに対して適切なHTMLを構築し, それにアセットを参照させることで, フロントエンドを描画します.

フロントエンドにおいては, 参照するサードパーティライブラリを必要最低限に抑えることで, コードベースをより統一的な管理下に置き,Misskeyの開発指針やデザインテーマが実効性を伴いやすくなっています. 結果,フロントエンドはVue.jsランタイム, 数百からなるコンポーネントと, ルーター (nirax) やストア (pizzax) といったアプリケーションを管理するためのシステム, そしていくつかの内製 (browser-image-resizer, buraha, etc.) および外製 (Chart.js, PhotoSwipe, etc.) サードパーティライブラリの組み合わせで構成されています.

Viteが生成するMisskeyのフロントエンドアセットは, 全体を合計すると,Blotli圧縮後のサイズでおよそ1.4MBにのぼります. このサイズが小さくなるよう努めることは, アプリケーションを提供するうえで重要な要素です.

  • JavaScriptCSSの成果物サイズが小さくなると, ユーザーエージェントがそれらを解析し, 実行する際のコストが削減されます.
    • 特に,JavaScriptは多くの場合,Webブラウザのメインスレッドで解析および実行されるため, 同程度のバイナリサイズで構成される画像ファイルなどと比較して処理にかかる負荷が非常に高く, その負荷を削減することは重要です.
    • また,JavaScriptCSSの成果物サイズが小さいということは, 多くの場合, それがシンプルであることを意味します. シンプルなコードは, 多くの場合, 軽快でパフォーマンスが高いといえます. つまり, コードサイズの削減は, パフォーマンスの観点からみても理にかなっています.
  • フロントエンドアセットのサイズが小さくなると, 当然ながら, ユーザーエージェントにそれらを配信する際の通信量が削減されます.
    • 高速通信技術が発展した現代においても, ユーザーが常にその恩恵を享受できる環境にあるとは限りません. 人と人のコミュニケーションを確立するアプリケーションとして, 不安定な通信環境においても, 快適性を可能な限り向上させるよう努めることは重要です.
    • アセットのサイズが小さくなると,より多くのアセットをCDNのキャッシュに蓄積させることができます. その結果, アセットのキャッシュヒット率が向上するので, ユーザーエージェントがアセットの取得に要する時間は, 削減されたアセットのバイナリサイズ分以上に短縮されることが期待できます.
      • 例えば,多くのサーバーが利用しているCloudflareでは, 同一ドメイン上でドライブファイルなどを配信すると, エッジキャッシュのバジェットがそれらと取り合いになります. これによってキャッシュヒット率の低下を招くと,逆にMisskeyの通信コストが非線形に増加する可能性を見積もれます.

先にも述べたように,フロントエンドのコードベースはその多くを数多のVue.jsコンポーネントで占めているわけですから,Vue.jsを効率的に活用することは, フロントエンドのアセットサイズ削減に直結し, ひいてはユーザー体験の向上につながるといえます.

MisskeyにおけるVue.jsの使用方法

Vue.jsは,世界で最も人気のあるUIフレームワークの一つです. 人気とは, 一朝一夕に獲得できるものではありません.Vue.jsにはモダンフレームワークなりの歴史があり, そして, 多種多様なフロントエンドの需要に応えるために, 様々な機能を提供して成長してきました. もっとも, ここまで読み進めている方の多くは, そんなことは百も承知かもしれませんが, とにもかくにも,Vue.jsの使い方は様々な形態があり, ユースケースに合わせて適切な使い方を選択することが重要です. とはいえ,その内のSFCを使用するか否か(使用しています) や,TypeScriptを使用するか否か(使用しています),およびComposition APIを使用するか否か(使用しています) については,先述のsyuilo連載「Misskey & Webテクロジー最前線」9月以上に掘り下げることが多くないので, ここでは割愛します.

代わりに, コンポーネントのスタイル連繫について見ていきましょう.Rich Web UIを謳うMisskeyは, 個々のコンポーネントに細かくスタイルをつけています. 先述の通り,Misskeyには数百のコンポーネントがありますから, スタイルデータはそれなりの量があります. そのため, スタイルがどのように管理され, 配信されるかは, 配信戦略において重要な要素の一つになります.

さて,HTMLWebブラウザにスタイルを提供する方法は,大まかに分けて3つあります.

<div style="color: red;">Hello, world!</div>
スタイル属性
<style>
.red {
  color: red;
}
</style>
<div class="red">Hello, world!</div>
スタイル要素
<link rel="stylesheet" href="style.css">
<div class="red">Hello, world!</div>
.red {
  color: red;
}
スタイルシート

このうち, 最後のスタイルシートによるスタイル連繫は, コンポーネントのロジック部分とスタイル部分が分離されることで, それぞれのライフタイムの長寿化を期待することができるため, プロダクションにおいては望ましい形式といえます. スタイルシートのスタイルルールは, セレクタを記述して,条件に合致する要素にスタイルを適用するようWebブラウザに指示します. セレクタは大局的なものから局所的なものまで多種多様な指定が可能ですが, コンポーネントのパーツに細かくスタイルをつけていくという状況においては, そのほとんどは局所的かつ単純なものになります. なお, 再利用性を担保してなるべくシンプルにセレクタを記述する方法は, 単一のクラス名を指定するのが, もっともパフォーマンスが高いとされています. この理由をきちんと説明するには,Webブラウザの実装の話などが大きく絡むので, ここでは割愛します.

MisskeyVue.jsに話を戻すと,SFCにはスタイルシートを直接記述できる機能が備わっています.この機能を使用してSFCにスタイルを直接記述すると,vue/compiler-sfc によってスタイルシートが抽出され,@vitejs/plugin-vueによって仮想モジュールとしてViteに参照されるようになり,最終的にViteがそれらをバンドルします. このおかげで,成果物として適切な様態でCSSが配信されることを保証しながら, 一方で開発体験としてはコンポーネントごとに関心を寄せてスタイルを記述できるようになります.

さて, 個々のコンポーネントが自由にスタイルを記述し, それを統合した場合, 実際にはそれらのルールが意図せず他のコンポーネントに影響を及ぼしたりする問題が予想されます.SFCの機能には, この問題を避けるため, スタイルをコンポーネントのスコープに閉じ込めるよう指示できるものがあります.スコープ付きCSSは, ビルド時にコンポーネント毎に一意の識別子を生成し, コンポーネント内の要素にそれを属性として割り当て, スタイルシートのセレクタにも書き足すことで, ユーザーのコード変更なしにスタイルをスコープに分離することができます.SFCのタグに属性を足すだけでドロップインに使用できる手軽さから,多くのVue.jsユーザーに使用され,Misskeyもかつて主方針として使用していました. しかしその実,スコープは完全ではなく, また, セレクタが肥大化してしまう問題も孕んでいました.

より踏み入った代替策として,SFCではCSSモジュールを使用することができます. これは, ビルド時にセレクタのクラス名を機械的に再構成し,そのバインドをJavaScriptで参照できるようにするものです. コンポーネントにおけるテンプレート内のクラス名は直接指定ではなくバインドされるフィールドへの識別子に置き換える必要があるので, コンポーネントのリファクタリングが必要ですが, スタイル連繋における課題点は概ね払拭されます.現在のMisskeyでは,ほとんどのコンポーネントがCSSモジュールを使用しています.

CSSモジュール注入の最適化

MisskeyCSSモジュールを使うようになった後のある日,syuiloは言いました.

:::fukidashi{chara="syuilo" charaName="しゅいろ"}

えー、CSS Modulesってminifyしてくれにゃいんだ

:::

https://misskey.io/notes/9fd9w06qah

このノートには,CSSモジュールのクラス名バインド用マップが成果物に丸々含まれていることを憂う気持ちが込められています. 例えば,次のようなSFCがあったとします.

<template>
  <div :class="$style.redColoredText">Hello, world!</div>
</template>

<style module>
.redColoredText {
  color: red;
}
</style>
赤色で挨拶文を表示するコンポーネント

このコンポーネントは次のように変換されて欲しいです.

export const HelloWorld = defineComponent({
  setup() {
    return () => jsx( // 実際にはより具象的なコードになる
      <div class="r3a9t">Hello, world!</div>
    );
  },
});
.r3a9t {
  color: red;
}
理想的な変換後のイメージ

しかし, 実際には, 次のように変換されてしまいます.

export const HelloWorld = defineComponent({
  setup() {
    return (_ctx) => jsx( // 実際にはより具象的なコードになる
      <div class={_ctx.$style.redColoredText}>Hello, world!</div>
    );
  },
});

HelloWorld.__cssModules = {
  $style: {
    redColoredText: "r3a9t",
  },
};
.r3a9t {
  color: red;
}
実際の変換後のイメージ

このようなことになってしまうのは, バインドの参照を常に静的に置換できるとは限らないためです. 例えば,$style.redColoredText のような参照は静的に置換できても,$style[color + "ColoredText"] のような参照はビルド時に color の値が定まるとは保証できないので, 静的に置換できません. また,Vue.jsComposition APIでは,useCssModule() を呼び出すことで, バインド用のマップ全体を取得することを許容しています. このような経緯で, 成果物にマップがそのまま含まれているのです. 逆に, それらの機能を一切使わないのであれば, 完全にそれらは無駄になっているといえます. 完全に無駄なものは安全に除去できるはずです. そこで,Misskeyでは,$style 配下を識別子のメンバーアクセスによる参照のみを認めるルールで運用することを前提に,静的置換を行うRollupプラグインを開発および使用することで, 成果物からマップを除去するようにしました. これにより,バンドルサイズの3% 程度の削減につながりました.

:::tip

詳細は #10923 を参照してください.

:::

今後の展望

現在まだ取り組まれていない最適化として, ルーティングの静的化を検討しています.記事の最初の方に提示した図を見るとRouterがレイヤーの中でも上部にあることがわかります. そのため,Page Componentsの読み込みはページが読み込まれてしばらくしてから始まります. しかし, どのルートがどのページを表示するかはビルド時にほぼ決定できると言って差し支えありません. この情報を静的に管理してバックエンドに連繫することで, バックエンドはより早いタイミングでユーザーエージェントに必要なアセットを知らせることができるので, ユーザー体験の向上を見積もることができます.

ここで,SFCの機能を利用して,

<template>
  <MkNoteDetailed v-model:note="note" />
</template>

<script setup lang="ts">
import type { Note } from 'misskey-js';
import { defineProps, ref, watch } from 'vue';

const props = defineProps<{
  noteId: string;
}>();
const note = ref<Note | null>(null);

watch(() => props.noteId, async () => {
  note.value = await os.api('notes/show', { noteId: props.noteId });
}, { immediate: true });
</script>

<route lang="yaml">
name: note
path: /notes/:noteId
</route>

といったようにページコンポーネントに直接ルーティング情報を記述できれば, ビルドの際ルーティング情報を抽出して静的に集約でき,ついでにpath propsも同一ファイル内で管理でき, 保守性の向上にもつながります.

あくまでも構想かつ一例にすぎませんが, このようにコンパイラの機能を使用するなどして,Misskeyの開発では今後も表層的な枠組みに囚われず, 野心的に様々なものを活用し, より良いユーザー体験に貢献できるよう努めていきたいと思っています.