ブラウザベースのMarkdown viewerとしてmoを作った

OpinionatedなMarkdown viewer

世の中にMarkdown viewerはたくさんありますが、自分がほしかったのは以下の特性を持つものでした。

  • CLIからMarkdownを開きたい
  • Markdownはブラウザで見たい(ローカルWebサーバ方式)
  • Markdownを開くたびに別プロセス/別ポートが使用されないようにしたい
  • グルーピングもしたい

また、コーディングエージェントがMarkdownドキュメントを書くようになり、CLIからファイルを追加できることがキーになってきました。 エージェントの行動を邪魔しないインターフェイスが求められています。

というわけで、OpinionatedなMarkdown viewerを作ってみました。

mo

github.com

moMarkdownファイルをブラウザで openするCLIツールです。Markdownファイルを渡すとブラウザが開いてレンダリングされたドキュメントが表示されます。

(Go + 組み込みReact SPAの)単一バイナリで、依存関係なしにインストールできます。

使い方

基本はファイルを渡すだけです。

$ mo README.md
mo: serving at http://localhost:6275 (pid 12345)

複数ファイルも渡せます。

$ mo README.md CHANGELOG.md docs/*.md

グループ

--target (-t) フラグでファイルをグループに分けられます。グループごとにURLパスとサイドバーが分かれます。

$ mo spec.md --target design      # http://localhost:6275/design
$ mo api.md --target design       # design グループに追加
$ mo notes.md --target plans      # http://localhost:6275/plans

設計ドキュメントは"design"グループ、コーディングエージェントが書いたプランファイルは"plans"グループ、という使い分けもできます。

moの仕組み

mo は結構私のユースケースに合わせて特徴的な動きをします。

mo はデフォルトでバックグラウンドでサーバーを起動します。最初の mo コマンドでサーバーが立ち上がり、シェルはすぐに返ってくるので、そのまま作業を続けられます。

$ mo README.md
mo: serving at http://localhost:6275 (pid 12345)
$ # シェルがすぐ使える

これが個人的にめちゃくちゃ使いやすいです。

コーディングエージェントにも使わせやすいですし、スクリプトにも組み込みやすいです。ブラウザオープンも --open--no-open オプションで制御可能です。

2回目以降の mo コマンドの実行も、同じポートですでにサーバーが動いている場合新しいサーバーは起動せず既存のサーバーにファイルを追加します。

$ mo README.md          # サーバー起動してファイル追加
$ mo CHANGELOG.md       # 既存のサーバーにファイル追加
$ mo docs/*.md          # さらに追加

つまり、mo コマンドを叩くたびにプロセスやポートが増えていくことはありません。1つのサーバーに対してファイルをどんどん追加していく設計です。

Webアプリケーションを1つ起動してそこにファイルを追加していくイメージです。Webアプリケーションを開発した経験のある私には馴染みのある動きです。

なお、完全に別のセッションが必要な場合は別ポートを使えば可能です。

$ mo draft.md -p 6276

サーバーの管理もCLIから行えます。これもWebサーバを管理したことがある人には馴染みがあるのではないでしょうか。

$ mo --status              # 稼働中のmoサーバーを一覧表示
$ mo --shutdown            # デフォルトポートのサーバーをシャットダウン
$ mo --shutdown -p 6276    # 特定ポートのサーバーをシャットダウン

Web UIからサーバーをリスタートすることもできます。

画面右下のリスタートボタンをクリックすると、開いているファイルやグループの構成を保ったままサーバーが再起動します。

mo をバージョンアップした後、ファイルを開き直すことなく新しいバージョンに切り替えられるので便利です。

Viewerとしての機能

Markdownのレンダリングは基本的なものには対応しています。

  • GitHub Flavored Markdown(テーブル、タスクリスト、脚注など)
  • Shiki によるシンタックスハイライト
  • Mermaid の図表レンダリング
  • GitHub Alerts(admonitions)

UI

UI周りの機能としては以下があります。

  • ダーク / ライトテーマ切り替え
  • 目次パネル(右側)
  • Rawマークダウン表示の切り替え
  • コンテンツコピー(Markdown / テキスト / HTML形式を選択可)
  • ドラッグ&ドロップでのファイル並び替え

UIはGitHubのMarkdownレンダリングに寄せたデザインにしています。最終的にGitHubでどう見えるかがわかることが重要ですし、何よりGitHubの見た目が一番慣れているので読みやすいです。

サイドバーのファイル一覧には、フラットビューとツリービューの2つの表示モードを用意しています。

フラットビューはファイル名だけを並べたシンプルな一覧で、ドラッグ&ドロップで自由に並び替えられます。少数のファイルをサッと確認したいときはこれで十分です。

一方、docs/*.md のように同じディレクトリの下に大量のファイルを開いた場合、フラットビューだと一覧が長くなって見づらくなります。ツリービューに切り替えると、ディレクトリ階層で折りたたんで表示できるので、ファイルが多くても整理しやすくなります。

ライブリロード

mo は開いたファイルの変更を監視しています。

ファイルが更新されると、SSE(Server-Sent Events)経由でブラウザに通知が飛び、自動でコンテンツが再取得・再描画されます。

アーキテクチャ

GoのHTTPサーバーにReact SPAを go:embed で組み込んでいます。go generate でフロントエンドをビルドしてから go:embed でバイナリに埋め込むので、最終成果物は単一バイナリになります。

バックエンドはGoで、ファイル管理・API・SSE・ファイル監視を担当しています。フロントエンドはVite + React + TypeScript + Tailwind CSSで、react-markdownを使ってMarkdownをレンダリングしています。

インストール

homebrew tap:

$ brew install k1LoW/tap/mo

manually:

releases page からダウンロードできます。

go install は残念ながらできません。

References

mo のアイデアは yusukebe/gh-markdown-preview をベースにしています。gh-markdown-previewはずっと愛用してきたツールで、CLIからMarkdownをブラウザでプレビューするという体験の原点です。そこに自分がほしかった機能を足していった結果、別のツールとして形になりました。

まとめ

mo を使うことで、複数のMarkdownファイルを持続的にプレビューできるようになりました。

特に気に入っているのは、バックグラウンドでサーバーが動いていて、mo コマンドでファイルをどんどん追加できるところです。シェルを掴まないのも推しポイントです。

是非使ってみてください。