Blazor アプリ開発で、ブラウザ上に表示されている HTML 要素から、元の .razor ファイルをたどる方法

# Blazor アプリ開発でこんなことで困ったことはないですか?

Blazor アプリ開発者の皆さんは、開発中、次のようなことは起きたりしていないでしょうか?

- アプリがだんだん大規模になり、.razor ファイルの本数や階層が増えて全体構造が頭に入りきらなくなり、**ブラウザ上に表示されるあの画面・あの部分をいじりたい**ときに、**どの .razor ファイルを開けばいいかパッと思い出せなくなってきた**
- それまで自分が担当していなかった既存の Blazor アプリの開発・保守を任されたが、コンポーネント構造を熟知しているわけもなく、**ブラウザ上に表示されるあの画面・あの部分をいじりたい**ときに、**どの .razor ファイルを開けばいいかパッとわからない**

今をさかのぼること 8 年前、ASP.NET Web Forms アプリを開発していた頃であれば、Visual Studio 2013 と ["Web Essentials" Visual Studio 拡張](http://vswebessentials.com/) を使うと、**ブラウザ上に表示されている HTML 要素をブラウザ上でクリックして選択す**ることで、**その HTML 要素をレンダリングしているソースの .aspx ファイルが Visual Studio エディタ内に開く**ことができ、前述のような困りごとはスマートに解決できたのでした。

このことは、今も Microsoft Docs サイトに解説が掲載されています (下記 URL)。

https://docs.microsoft.com/en-us/aspnet/visual-studio/overview/2013/visual-studio-2013-web-tools#task-3---updating-a-style-property

上記 Web Essentials とは違うのですが、Visual Studio 2012 の頃から似たようなことが可能で Microsoft Channel9 に今でも動画が残っており、動作を見るにはこちらのほうがわかりやすいかもしれません。

[![fig0](https://github.com/jsakamoto/Toolbelt.AspNetCore.CssLiveReloader/blob/master/.assets/fig2.png?raw=true)](https://channel9.msdn.com/Blogs/ASP-NET-Site-Videos/visual-studio-2012-page-inspector) _["Channel 9 | ASP.NET Site Videos | Visual Studio 2012 Page Inspector"](https://channel9.msdn.com/Blogs/ASP-NET-Site-Videos/visual-studio-2012-page-inspector)_

さておき今は 2021 年。
かつ、ASP.NET Web Forms ではなく Blazor 開発における話です。

しかしどういうことでしょう、どうやら Blazor 開発においては、"ブラウザ上に表示されている HTML 要素から、ソースの .razor ファイルを開く" という機能は提供されていないようなのです。

いちおう、"Web Live Preview" という Visual Studio 拡張が Microsoft の手によって開発が進行中で、これは前述の Web Essentials 相当の機能を、Visual Studio 2019 以降に提供してくれます。

https://devblogs.microsoft.com/aspnet/introducing-web-live-preview/

ただしまだ開発途中・プレビュー段階ということもあり、対応されているフレームワークは、.NET Framework 上の ASP.NET Web Forms および MVC のみ、ということらしいのです。

もちろん、Blazor への対応も視野には入れてくれているようです。
ですがとにかく、まだ現在時点では実現していない模様... (参考: 下記ツイート)

https://mobile.twitter.com/sayedihashimi/status/1415378630700589058

様々な人たちが努力し支え合ってきたお陰で、今の快適な開発環境があることは重々承知していますし、感謝しています。

とはいえ、**8 年前よりも、2021 年の今の方が、開発者体験が劣ってる**っていうのは、8 年前にいちど快適な体験をしてきたあとではなおのこと、なかなかに堪えるものがありました。

# 待ちきれなくて、劣化版だけど、自分で作ってみた

...ということで、Web Live Preview の Blazor 対応をついに待ちきれなくなったため、**類似の機能を自分で作り上げる**に至りました。

それが **"Find Razor Source File"** (下記 GitHub リポジトリ) です。

https://github.com/jsakamoto/FindRazorSourceFile

"Find Razor Source File" は、一連の NuGet パッケージのセット、および、Visual Studio 拡張を含む、Blazor アプリ開発者向けのツールセットです。

"Find Razor Source File" は、Blazor アプリ開発で、**ブラウザ上に表示されている HTML 要素から、元の .razor ファイル名を表示**し、さらには Visual Studio で開発中であれば、**その .razor ファイルを Visual Studio エディタ内に開く**、という機能・開発者体験を提供します。

## "Find Razor Source File" が提供する開発者体験

詳しい手順は上記 GitHub リポジトリの README を参照いただくとして、ひとたび、Blazor アプリプロジェクトに 

- "Find Razor Source File" の NuGet パッケージ参照の追加、
- および、若干のソースコードへの加筆

を済ませると、その Blazor アプリの実行時、ブラウザ画面上で **Ctrl + Shift + F** のキーボードショートカットを入力することで、画面が暗転、"検査モード" に切り替わるようになります。

この "検査モード" の状態で、表示されている画面上でマウスカーソルを動かすと、**マウスカーソルの下にある HTML 要素をレンダリングした .razor ファイルのファイル名がツールチップ表示**されます。

![fig](https://github.com/jsakamoto/FindRazorSourceFile/blob/master/.assets/fig2.png?raw=true)

なお、この "検査モード" の機能は、Visual Studio とは独立したBlazor アプリそのものに注入される機能となります。
そのため、Visual Studio Code での開発時はもちろん、.NET CLI からの実行でも機能します。
(※ "Find Razor Source File" は、.NET5 以降の Blazor WebAssembly および Blazor Server の両タイプをサポート。デバッグビルドでのみ動作します)

そしてさらに、開発作業に Visual Studio 2019 を利用している場合、予め **"Find Razor Source File - Browser Link Extension"** Visual Studio 拡張 (下記 URL) を Visual Studio 2019 にインストールしておけば、

https://marketplace.visualstudio.com/items?itemName=jsakamoto.findrazorsource-browserlink-vsix

前述の "検査モード" 中に、**マウスで HTML 要素をクリックすると、その HTML 要素のレンダリング元の .razor ファイルが Visual Studio エディタ内に開く**ようになります (下記 GIF アニメ)。

![fig](https://raw.githubusercontent.com/jsakamoto/FindRazorSourceFile/master/.assets/movie-001-vs2022.gif) (※~~本稿投稿時点では、この Visual Studio 拡張はバージョン 2019 のみサポート~~ 2022年1月9日現在では、この Visual Studio 拡張は、Visual Studio 2019 および Visual Studio 2022 用に提供されています)

なお、"Find Razor Source File" を組み込んだ Blazor アプリ、とりわけ Blazor WebAssembly アプリですが、リリースモードで発行したあとには、"Find Razor Source File" の各種アセンブリは発行結果から削除されるようになっています。
つまり、リリース時の Blazor WebAssembly アプリのコンテンツサイズを、"Find Razor Source File" が増やすことはありません。

## 動作原理

"Find Razor Source File" の動作原理ですが、実はこれ、Blazor における**「CSS 分離」**機能を利用しています (公式ドキュメントの URL は下記)。

https://docs.microsoft.com/en-us/aspnet/core/blazor/components/css-isolation?view=aspnetcore-5.0

CSS 分離機能が作用していると、たとえば `Foo.razor` というコンポーネントで以下のように HTML をマークアップしていた場合、

```html:Foo.razor
<h1>Hello World</h1>
```

実際にブラウザにレンダリングされる HTML には、`b-3xxtam6d07`のような `b-～` で始まる属性が追加されます。

```html:Foo.razor...のレンダリング結果
<h1 b-3xxtam6d07>Hello World</h1>
```
この `b-～` で始まる属性は、.razor ファイルひとつひとつに割り当てられるある種のハッシュ値です。

つまり、この CSS 分離のために HTML 要素に追加される `b-～` で始まる属性値と、.razor ファイルの対応表 (下記イメージ) が作れるのです。

属性値        | .razor ファイル名
-------------|--------
b-n3o2mhseko | Pages\Index.rzor
b-1s51sonau2 | Pages\Counter.rzor
b-69jeqg1gtr | Shared\SurveyPrompt.razor
...          | ....

"Find Razor Source File" では、Blazor アプリプロジェクトのビルド時に上記 "対応表" を生成し、これを利用して機能を実現しています。

なお実は、`~.razor.css` CSS 分離ファイルが存在しない .razor コンポーネントについては `b-~` 属性の付与は行なわれません。
これは、不用意にコンテンツサイズを膨張させないための仕様なのでしょう。

しかし前述のとおり、CSS 分離用の属性値を手がかりにソースの .razor をたどる原理上、これでは困ります (`~.razor.css` CSS 分離ファイルが存在しない .razor ファイルは辿れないことになってしまいます)。

そこで、"Find Razor Source File" NuGet パッケージによって注入されるビルドスクリプトでは、すべての .razor コンポーネントについて `b-~` 属性を追加してレンダリングするよう強制しています。
(※ただし強制するのはデバッグビルドのみです)

## "Find Razor Source File" の限界

逆に言えば、この動作原理が、"Find Razor Source File" の機能・性能の限界にもなっています。

まず、ブラウザに表示されている HTML 要素から割り出せるのは、".razor ファイルの単位" まで、です。
.razor ファイル内の、どの行・どの箇所で、その HTML をレンダリングしたのか、という粒度までには割り出せません。
冒頭で紹介した Web Essentials + ASP.NET Web Forms ですと、要素単位でもとのマークアップを辿れるのですが、"Find Razor Source File" では上記動作原理ですので、ファイル単位までしか辿れません。

また別の限界として、`b-～` で始まる属性が追加された HTML を出力しない .razor ファイル、というのが割と容易にあり得ます。

すなわち、下記例 `Bar.razor` のように、他の .razor コンポーネントしかマークアップに含まれていない場合、

```html:Bar.razor
<MudAppBar ...>
  ...
</MudAppBar>
```
この `Bar.razor` コンポーネントがレンダリングする HTML には、`Bar.razor` コンポーネントそれ自身の CSS 分離用属性が付与された要素が存在しません。

```html:Bar.razor...のレンダリング結果
<!--
Bar.rzor 自身が直接レンダリングする HTML 要素がなかったので、
Bar.razor に紐付く b-~ 属性値を持った HTML 要素が一切存在しない!
-->
<header class="mud-appbar mud-elevation-4 mud-theme-primary">
  ...
</header>
```

このような場合、"Find Razor Source File" では、元の `Bar.razor` を探し当てることができません。

"Find Razor Source File" は、まだ開発したてで、稼働実績も少ないため、他にもまだまだ問題課題や限界があるかもしれません。
とりあえず、今のところわかっている機能・性能の限界は、以上のとおりです。

## 他の IDE 等への対応

あいにくと、自分は Visual Studio 以外の高機能エディタや IDE 等の拡張 (アドイン) を実装する技量を持ち合わせておりません。
そのため、"Find Razor Source File" は Visual Studio 2019 用拡張を提供するに留まっています。

しかしところで、"Find Razor Source File" を仕込んだ Blazor アプリ (前述のとおり、これ自体は Visual Studio を関係なく動作する) は、"検査モード" 中に HTML 要素をクリックすると、`razorsource:lockin` というカスタム DOM イベントを、`document` 要素を対象に発火させています (下記 GIF アニメ)。

![fig](https://github.com/jsakamoto/FindRazorSourceFile/blob/master/.assets/movie-002-custom-event-is-fired.gif?raw=true)

実のところ、"Find Razor Source File - Browser Link Extension" Visual Studio 拡張は、このカスタム DOM イベントを捕捉して動作しているに過ぎません。

ですので、上記カスタム DOM イベントを補足・連携することで、"Find Razor Source File" Visual Studio 拡張と同様の拡張機能を、他の高機能エディタや IDE 等にも開発できるのではないかと想像されます。

もしどなたか腕に覚えのある方がいらっしゃれば、挑戦してみるのも面白いかもしれませんね。

## 2022/01/09 追記 - VSCode 対応しました

下記 GIF アニメのとおり、ページ右下に配置された「Find Razor Source File」設定ボタンから「クリックしたコンポーネントの.razorファイルをVSCodeで開く」オプションを有効にすると、クリックしたコンポーネントの .razor ファイルが Visual Studio Code エディタで開かれるようになりました。

![fig](https://github.com/jsakamoto/FindRazorSourceFile/blob/master/.assets/movie-001-vscode.gif?raw=true)

これで、Visual Studio Code を使って Blazor アプリ開発を行なっている場合は、Windows OS 上での開発に限定されず、macOS や各種 Linux ディストリビューション上であっても、"Find Razor Source File" による .razor ソースファイルへのジャンプを利用できるようになりました。

なお、この VSCode 対応は、下記ツイートで「URL Handler の仕組みで VSCode でファイル開けるじゃん!」ってことに気づかされて、それでようやくに実現できました!

https://twitter.com/mizchi/status/1474029047076487168

上記ツイートの続きが、下記記事にまとめられています。

https://zenn.dev/mizchi/articles/introduce-tsx-source-jump


# まとめ

以上のとおり、"Find Razor Source File" を実装したことで、これでようやくに、8 年前の ASP.NET Web Forms 開発当時の開発者体験を、**(少しだけですが) 現代の Visual Studio における Blazor 開発に取り戻す**ことができました。

いずれ将来、Web Live Preview が Blazor 対応を果たした暁には、その一部機能を限定的に再現しただけの劣化版である "Find Razor Source File" は用済みとなることと思います。

とはいえ、それまでの間、つなぎとして、"Find Razor Source File" が少しでも多くの Blazor 開発者の体験を改善することができればと願う次第です。