okayurisotto.net

私が好きでやったことが他の人のためにもなったらお得かも!

Misskeyのソースコードの読み方

  1. 📝
  2. 🔄

はじめに

Misskeyの開発に貢献するためにそのソースコードを読む上で知っておくとよいことを軽くまとめます。貢献に挑戦しようとしている人の助けになれば幸いです。

なお、技術的な解説(Misskeyが依存しているパッケージの使い方など)についてはOut of Scopeです。それらについて知るには、それらのドキュメントを読んだり実際にソースコードを読む方がずっと早いですので。この記事は、「文書化が不十分な箇所があるMisskey開発でどのように情報を得るか」について扱ったものです。

Misskey開発で使われているもの

Gitがバージョン管理のために使われていて、GitHubでプロジェクト全体が管理されています。GitHubをやめるという話もありましたが(Issue #8886)現在のところ大きな動きはないようです。

Misskeyはバックエンド・フロントエンドともにTypeScriptで開発されています。npm(pnpm)を使ったパッケージ管理などについての知識と、TypeScriptによってJavaScriptに追加される静的型に関するある程度の知識が必要になります。Node.jsをやめるという話も(話だけ)ありましたが、現時点では大きな動きはありません。

バックエンドではフレームワークとしてNestJSを使用していて、packages/backend/src/server/以下でよく見られます。NestJSのDependency Injectionを使って開発されている関係で、import文をimport type文に書き換えるなどすると動かなくなることがあるようです(当事者である私も詳しくない……)。

MisskeyのNestJSのDIについてはこちらの記事を参照してください。

データベースを扱う際のORMとしてはTypeORMを使用しています。packages/backend/src/models/entities/以下でよく見られます。

ジョブキューの実装としてBullMQが使われています。詳しくはこちらの記事を参照してください。

ただBullMQを使った現時点のMisskeyには何らかの問題があり、パフォーマンスが悪化しています(Issue #11000)。

フロントエンドではUI構築のためのフレームワークとしてVue.jsを使用しています。.vueという拡張子のファイルは、Vue.jsのSFC(Single File Component)というものです。コンポーネントごとにファイルが分けられているということです。

niraxはMisskeyがフロントエンドで使用しているオリジナルのルーティングシステムです。CONTRIBUTING.mdまたはpackages/frontend/src/nirax.tsを参照してください。

pizzaxはMisskeyがフロントエンドで使用しているオリジナルの「lightweight store」です。packages/frontend/src/pizzax.tsを参照してください。

その他、テストフレームワークとしてはJestが、リンタとしてはESLintが、UIコンポーネント開発ではStorybookが、サーバーによるクライアントの配信時にPugが使われています。

Misskey開発ではエディターとしてVisual Studio Codeを使用することが想定されています。.vscode/extensions.jsonには、VS Codeで開発するときに使用を推奨される拡張機能が列挙されています。とりあえずこれらは入れておきましょう。また、Dev Containersを使った開発ができるようにもなっています。

git clone時の注意点

後述するgit blameを活用した読み方では、過去のコミットの内容がすでにローカルリポジトリに存在していることが、速度的に重要になってきます。ですので、--depth=1--filter=blob:noneのようなcloneはせず、最初からすべてをcloneしておきましょう。

Contribution guideについて

CONTRIBUTING.mdのことです。まずここを見ましょう。あなたが何をしたいのかにもよりますが、とりあえず一読はしておくと後がスムーズです。

package.jsonについて

現時点ではMisskeyは、バックエンドに関してもフロントエンドに関しても、TypeScriptによって開発されています。そしてpackage.jsonはNode.jsで開発を行うときに登場する、依存している外部のパッケージが書かれたファイルです。ソースコードは、このファイルに書かれインストールされた依存先からモジュールをimportすることがよくあります。何か見覚えのないものがあったらまず、それをimportしている文からパッケージ名を知り、package.jsonと照合、勘違いではないようならそのパッケージについてググる……という手順を踏むことになるでしょう。

packagesについて

Misskeyではbackendfrontendという名前でそれぞれの機能が分割されています。いわゆるmonorepoというものです。下に、現時点でのそれぞれについてまとめます。しかしIssue #8168で今後変化していくことが予想されるため、あくまで現時点での情報です。

  • backend
    • 名前通りバックエンド
    • データベースを扱うのはこちら
    • APIエンドポイント(/api/**/*)とそのSpec(/api.json
    • ActivityPubでの連合
    • frontendの配信
  • frontend
    • 名前通りフロントエンド
    • Webブラウザの開発者ツールでデバッグするものなど
    • Vue.jsを使っているのはこちら
  • sw
    • Service Worker
    • プッシュ通知
  • misskey-js
    • backendが提供するAPIをfrontendが使うときのもの
    • backendが提供するAPIをswが使うときのもの
    • misskey-dev/misskey.jsで開発されていたが統合された(Issue #10322
    • APIの型定義やスキーマ定義なども、backendではなくこちらで行おうという動きがある(PR #10752
  • shared
    • 共有されるリソースを置く場所
    • 現時点では詳細不明

このmonorepo的な管理は最初からされていたものではありません。この変更はIssue #7779で議論され0e4a111で行われた編集です。当該コミットのメッセージには、Issue IDの記載こそありますが「refactoring」としか書かれていないため、重要でないと勘違いしてしまいそうになります。気を付けてください。

また、実際に0e4a111にcheckoutしてみるとわかりますが、frontendclientと呼ばれていた時期がありました。これは9384f53で行われた編集です。このコミットと関連するIssueやPRは未発見で、どのような経緯で行われたのかについてはわかりません。

Gitを使って編集内容からコミットハッシュを得る

私は上記monorepo化に関する情報を探るためにGitを使いました。Gitではいくつかの方法で履歴を検索できます。Gitはサブコマンドや引数の仕組みが難解であまり使いたくないものではありますが、その点を我慢すると非常に強力でありがたいものです。

最も基本的なものは、git log <path>です。これはそのファイルに関するコミットを一覧表示するものです。git log packages/backend/package.jsonというように実行することで、backendpackage.jsonがどのように編集されてきたのか確認することができます。最も下に、前述した0e4a111が表示されるはずです。

次に覚えておきたいのが、git log --follow <path>です。これは--followオプションによって、そのファイルのリネーム前についても対象とする形で一覧表示するものです。実際にpackages/backend/package.jsonに対して実行すると、0e4a111以前のコミットも表示されるようになります。

git log --unifiedではunified形式のdiffを表示することができます。こちらも便利です。

とりあえずこれだけ覚えておけばなんとかなるかもしれないというのが、git blame <path>です。これは、<path>で指定した現在のワークツリーにあるファイルのすべての行に対し、それぞれいつ編集されたものなのかを表示します。ファイルの中にある問題のある記述が具体的にいつ追加されたものなのかを知るときに使います。「blame」とは「落ち度を非難する」というような意味の英単語です。それを聞くとだいぶテンションが下がりますが、気にしてはいけません。

そして基本中の基本ではありますが、git checkout <commit>です。ブランチにcheckoutする場面が多いので勘違いしがちですが、このコマンドはワークツリーのファイルを任意の時点に巻き戻すようなものです。コミットハッシュを渡せば、その時点のワークツリーを復元することができます。前述したgit blameでは、その行が編集された最後のコミットしか見れません。ですのでgit blameを使ってコミットハッシュを得るには、次のような手順を踏むとよいでしょう。

  1. git blame <path>でそれらしいコミットハッシュを得る
  2. git show <hash>で具体的な編集内容を調べたり、git show <hash>:<path>でその時点のファイルの内容を見る
  3. もしその編集が求めるものではなかった場合、git checkout <hash>^したあと手順1に戻る

もっと賢いやり方があるかもしれませんが、とりあえずこれで大抵の場合はなんとかなると思います。ただしcheckoutはワークツリーを大きく書き換えるため、VS Codeの拡張機能などが動いている状態で実行すると、拡張機能がエラー通知を表示してくることがあります。こればっかりは仕方がないのでそういうものだと思うようにしましょう。

Issueの読み方

何か修正したい挙動があるならまず真っ先に(Issue検索などに取りかかるのではなく)ソースコードを読んで、その挙動が発生している機能についての「名前」を確認することを勧めます。そして名前がわかった段階で初めてGitHubでIssue検索をすべきです。これについては何もMisskey開発に限った話ではないでしょう。機能名を間違って認識しているとその後の作業や情報共有にも響いてきますので、できる限り早い段階で正確な名前を知っておくべきです。

Issue検索では、日本語と英語の両方で検索するとよいでしょう。Misskey開発で使われている言語は主にこの2つです。しかし、そもそもMisskey開発ではIssue機能が適切には使われていない場面があるので期待してはいけません。粒度がまちまちだったり、何に困っているのかがしっかりとは文章にされていなかったり、奇声が発せられていたり、異なる複数の問題が1つのIssueで語られていたり、そもそも作業がIssueと紐付いていなかったりします。実際のソースコードとGitを頼るのがよいでしょう。

PRにも期待はしない

同上です。修正がPRを介さず直接開発用ブランチにコミットされることもあるため、PRへの期待は禁物です。コミットを得るのが先です。

コミットメッセージには期待しない

そのコミットが明確にIssueと紐付いていたとしても、コミットメッセージにIssue IDが書かれないことがあります。期待してはいけません。情報量のないメッセージや暴言や「なんかもうめっちゃ変えた」を深く捉えてはいけません。

コミットからIssueやPRを探す

「コミットについてはすでにわかっているもののそのコミットがされた経緯をどうしても知りたい」という場面では、ダメ元でIssueやPRを探してみるのも悪くはないでしょう。いくつかの方法があります。

そのコミットのハッシュで検索するというのが最もシンプルで効果的なものでしょう。コミットメッセージにIssueのIDが書かれていなくとも、Issueにコミットハッシュが書かれていることはあります。

例えば、私の短い貢献活動の中で複数回困らせられたコミットに、b75184eがあります。編集の量が膨大であるために、git blameしているとしょっちゅうこのコミットハッシュを見ることになります。コミットメッセージは「なんかもうめっちゃ変えた」のみ。IssueやPRのIDも書かれておらず、情報量はゼロです。実際の変更内容を見ればある程度推測はできますが、それでもだいぶ厳しいものです。このコミットハッシュで検索すると、Issue #9077PR #9081と関連したものであることがわかります。このコミットは、テストフレームワークであるMochaをJestに変更しようというものでした。(とは言っても、このIssueに残された情報はほとんどが奇声で、具体的にMochaの何が嫌で何をどうすることでどうしたかったのか、Jest移行により何がどうなることが期待されるのかなどについての情報は依然として得られないのですが。)

コミットのハッシュで検索しても見付からなかった場合、そのコミットと関連したIssueやPRは存在しないかもしれません。しかしこれは「かもしれない」であって、実際にはわかりません。究極的な手段として、「コミットの日時からIssueやPRを絞り込む」という手段があります。GitHubでは検索パラメータを使って、IssueやPRが作成された日時やcloseやmergeされた日時から絞り込むことができます(GitHubのヘルプを参照してください)。とりあえず、コミットが作成される前までにIssueやPRが作成され、コミットが作成された後にそれがcloseされた、というような条件を書くといいかもしれません。

is:issue is:closed created:<YYYY-MM-DD closed:>YYYY-MM-DD hoge

ソースコードには期待しない

ここまで「実際のソースコードを頼れ」と言ってきましたが、頼るだけで期待はしてはいけません。Misskeyでは、わかりやすさのためにデッドコードをあえて残しておくことがあります。そのデッドコードがデッドコードでないことを期待してしまうと、Issue #10745での私のように、「デッドコードがうまく動いていないIssue」を作成してしまいます。使われるべきコードが使われていないために問題が起きているようなときは、そのコードが「あえて残されたデッドコード」であるかもしれないことに注意しておく必要があるということです。

またデッドコード云々を抜きにしても、anyによる型エラーの握り潰しなどが(故意ではないにしろ)様々な場面で行われているため、もし読んだり書いたりしたい場所の近くにanyを見かけたら、まずはよりよい型定義を行うリファクタリングをすることになるでしょう。

何に期待すればいいのか

コンポーネント名や関数名などの非常に基本的な情報は、機能についての理解を深めるのに役立ちます。コミットの親子関係や作成日時、IssueやPRの作成日時などは実装の経緯を推し量るのに重要な役割を果たします。Misskeyが依存しているパッケージのドキュメントについては、普通レベルに信頼できるでしょう。VS CodeのReference機能は、その変数がどこでどう使われているのかを素早く把握できてとても便利です。

おわりに

私はいろいろとキツかったのでMisskey開発への貢献はやめてしまいましたが、これから貢献しようとしている人の役に立てたらと思い、開発時に残していた個人的なメモを元にこれを書きました。頑張ってください。

追記(2023/07/08)

Misskey開発への貢献に再挑戦してみています。この記事に関しても随時更新していく予定です。

追記(2023/09/28)

貢献をやめました。技術的にも雰囲気的にも、Misskey開発には厳しいものがあります。