Laravelプロジェクトのルート一覧をVSCodeのサイドバーに表示し、行をクリックするとコントローラのメソッドやルート定義へジャンプできる拡張「Laravel Routes Explorer」を作りました。
想定読者
VSCode拡張を一度作ってみたいエンジニアと、Laravelの開発で「このURLはどのコントローラか」を探す手間を感じている方です。VSCode Extension APIの知識は前提にしません。
ソースコードとvsix(VSCode拡張のパッケージファイル)は GitHub で公開しています。
はじめに
ソーイ株式会社村上です。
ソーイでは熊本で3週間インターンという採用活動を行っています。ソーイの普段の開発業務はLaravelが中心で、インターン生にもLaravelで書かれた実際のプロジェクトに触れてもらいます。
参加される方の多くは、大学でプログラミング言語の基本を学んだ状態です。一方でWebフレームワークの知識はほぼない、というのが典型的なスタート地点です。
そのため、バグ調査をお願いする場合でも、いきなり原因を探してもらうわけではありません。AIを使って調査できるとはいえ、まずは「そのソースがどこにあるか」「今どういう実装になっているか」を自分で確認してもらうところから始めます。
ここで毎回つまずくのが、エンドポイントとソースコードの対応です。「この画面のURLはどのコントローラで処理されているのか」を繰り返し教えることになります。しかも次のような事情で、説明が一般化しにくいのです。
- プロジェクトごとにフォルダ構成が微妙に違う
- ルートファイルが
routes/web.phpだけでなく複数に分かれているケースがある - ルートグループの
prefixやnameで、ファイル上の記述と実際のURLが一致しない
つまり、ゼロからプロジェクトを読み始めるにはLaravelの基本知識が前提になってしまいます。その前提を少しでも下げるために、エンドポイントの一覧からソースコードへ直接飛べるVSCode拡張を作ることにしました。
作ったもの
Laravel Routes Explorerという名前のVSCode拡張です。
- アクティビティバーのアイコンからルート一覧を表示。行ごとにHTTPメソッド、URI、ルート名、コントローラ@メソッドを表示
- HTTPメソッドごとにアイコンの色を変える(GET=緑、POST=青、PUT/PATCH=黄、DELETE=赤)
- 行をクリックするとコントローラの該当メソッド行を開く。Invokableコントローラは
__invokeへ、クロージャはルート定義行へ - 右クリックで「Go to Route Definition」を選ぶと
Route::get(...)の定義行へ飛ぶ - パス(URIの部分一致)とミドルウェア(複数選択でAND条件)で絞り込み
-
routes/配下の変更を監視して自動で再読み込み。編集途中の構文エラーで失敗しても前回の一覧を残す -
route:listのコマンドとプロジェクトの場所を設定で変えられるので、DockerやSail、モノレポ構成でも使える
インターン生の使い方としては、画面のURLを見てルート一覧から該当行を探し、クリックしてコントローラに入る、というのが基本の動線です。ミドルウェアの絞り込みは「認証が必要なエンドポイントだけ見たい」といった場面で使います。
インストール方法
Marketplaceには公開していないので、GitHub Release からvsixをダウンロードしてインストールします。VSCodeのプロファイルを使っている場合は --profile <名前> を付けてください。
code --install-extension laravel-routes-explorer-0.2.0.vsix
PHPがDockerの中にある場合は、プロジェクトの .vscode/settings.json で route:list のコマンドを差し替えます。Laravel Sailなら vendor/bin/sail artisan route:list --json です。
{
"laravelRoutes.command": "docker compose exec -T app php artisan route:list --json"
}
開発環境
| 項目 | バージョン |
|---|---|
| VSCode | 1.138 (拡張の対応は1.85以上) |
| Node.js | 24 |
| pnpm | 10 |
| TypeScript | 5.5 |
| @vscode/vsce | 3.x |
| 動作確認に使ったLaravel | 13.32 (対象はLaravel 11以上) |
初めてのVSCode拡張で押さえたポイント
1. 最小構成は package.json と extension.ts の2つ
VSCode拡張の本体は、package.json の contributes に「何をUIに追加するか」を宣言し、src/extension.ts の activate() で実際の処理を登録する、という2段構えです。
今回のサイドバー表示に必要な宣言は次の3つでした。
{
"contributes": {
"viewsContainers": {
"activitybar": [
{ "id": "laravelRoutes", "title": "Laravel Routes", "icon": "resources/icon.svg" }
]
},
"views": {
"laravelRoutes": [
{ "id": "laravelRoutes.routes", "name": "Routes" }
]
},
"commands": [
{ "command": "laravelRoutes.refresh", "title": "Laravel Routes: Refresh", "icon": "$(refresh)" }
]
}
}
viewsContainers がアクティビティバーのアイコン、views がその中のビュー、commands がボタンやメニューから呼ぶコマンドです。ビューを表示するとVSCodeが拡張を自動で起動してくれるので、activationEvents は空で構いません。
そして extension.ts 側で、ビューにデータを供給する TreeDataProvider を登録します。
export function activate(context: vscode.ExtensionContext): void {
const provider = new RouteTreeProvider();
const treeView = vscode.window.createTreeView('laravelRoutes.routes', { treeDataProvider: provider });
context.subscriptions.push(
treeView,
vscode.commands.registerCommand('laravelRoutes.refresh', () => load()),
);
}
デバッグは .vscode/launch.json に extensionHost の設定を書いておけば、F5で「拡張開発ホスト」という別ウィンドウのVSCodeが立ち上がり、開発中の拡張がそのまま動きます。launch.jsonの args に開きたいフォルダを足しておくと、毎回サンプルプロジェクトを開いた状態で起動できて便利でした。
2. TreeViewかWebviewか - TreeViewの制約を先に知っておく
サイドバーに一覧を出す方法は2つありました。VSCode標準のTreeView(エクスプローラーと同じ見た目のツリー表示)と、HTMLを自由に描けるWebviewです。
最初は「一覧の上にフィルター入力欄を置きたい」と考えていました。ところがTreeViewにはテキスト入力欄を埋め込めません。ラベルの文字色も変えられません。実装を最小限にするため、やりたいことをTreeViewの標準機能でどう実現できるか整理しました。
| やりたいこと | TreeViewでの実現方法 |
|---|---|
| フィルター入力欄 | ビュータイトルのボタンからQuickPick(コマンドパレットと同じ形式の選択UI)を開く |
| フィルター中の条件表示 |
TreeView.message に文字列を出す |
| メソッドごとの色分け |
ThemeIcon に ThemeColor を渡してアイコンの色を変える |
| 件数表示 | TreeView.badge |
| プロジェクト未検出時の案内 |
viewsWelcome で文章とリンクを出す |
結果としてTreeViewを選びました。理由は、テーマ追従・キーボード操作・仮想スクロールが標準で付いてくるため、実装コストが下がるからです。
色分けは次のように書きます。charts.* はVSCodeのテーマが定義している色なので、ダークテーマでもライトテーマでも自然に見えます。
const METHOD_COLORS: Record<string, string> = {
GET: 'charts.green',
POST: 'charts.blue',
PUT: 'charts.yellow',
PATCH: 'charts.yellow',
DELETE: 'charts.red',
ANY: 'charts.purple',
};
this.iconPath = new vscode.ThemeIcon(
'circle-filled',
new vscode.ThemeColor(METHOD_COLORS[route.methods[0]] ?? 'charts.foreground'),
);
フィルターは、取得済みのルートからミドルウェア名を集めてQuickPickに渡すだけです。
const picked = await vscode.window.showQuickPick(items, {
canPickMany: true,
placeHolder: 'ミドルウェアを選択(複数選択でAND条件)',
});
3. データ源は php artisan route:list --json
ルート情報はLaravel自身に出してもらうのが確実です。php artisan route:list --json を実行すると、次のような配列が返ってきます。
{
"domain": null,
"method": "GET|HEAD",
"uri": "admin/dashboard",
"name": "admin.dashboard",
"action": "App\\Http\\Controllers\\Admin\\DashboardController@index",
"middleware": ["web", "auth", "verified", "throttle:60,1"],
"path": null
}
設計段階で想定していたことと、実際の出力とで食い違っていた点がありました。
クロージャルートには path フィールドがある。 Laravel 12.55以降では、クロージャで定義したルートに "path": "routes/web.php:12" のように定義位置が入ります(laravel/framework #59237、2026年3月に12.xへマージ)。これのおかげでクロージャへのジャンプは正確にできます。それより前のバージョンにはこのフィールドがないので、後述する検索にフォールバックしています。
実装上の細かい注意点もいくつかあります。
-
methodはGET|HEADのように結合されているので、GETがあればHEADは隠す。7メソッド全部ならANYと表示する - 標準出力にJSON以外(deprecation警告など)が混ざることがあるので、最初の
[から最後の]を切り出してからパースする - ルートが0件のときはJSONではなくメッセージだけが出るので、空配列として扱う
弊社の案件はDockerで動かすものが多く、ホストにPHPがない環境が普通にあります。そこでコマンド文字列全体を設定 laravelRoutes.command にして、シェル経由で実行する形に変えました。
{
"laravelRoutes.command": "docker compose exec -T app php artisan route:list --json"
}
4. クラス名からファイルを引くには composer の PSR-4 マップを読む
action に入っているのは App\Http\Controllers\UserController@index のようなクラス名です。ここからファイルパスを求める必要があります。
素朴には App\ を app/ に置き換えれば動きますが、それだと Route::view() や Route::redirect() が使うフレームワーク側のコントローラや、パッケージが提供するルートには対応できません。
そこでcomposerが生成する vendor/composer/autoload_psr4.php を読むことにしました。PSR-4は名前空間とディレクトリの対応を決めるPHPの規約で、composerはその対応表をこのファイルにPHPの配列として書き出しているので、正規表現で抜き出します。
'App\\' => array($baseDir . '/app'),
'Laravel\\Sanctum\\' => array($vendorDir . '/laravel/sanctum/src'),
プレフィックスが長いものから順にマッチさせ、残りの名前空間を / に変えて .php を付ければファイルパスになります。PHPを起動せずにファイルを読むだけなので速く、Docker環境でもソースがマウントされていれば動きます。
メソッド行は開いたファイルを function\s+index\s*\( で検索して探しています。public static function のような修飾子の違いを気にせずに済むよう、function から後ろだけを見る形にしました。
5. ルート定義行の検索。prefixグループとリソースルートをどう扱うか
コントローラで処理されるルートについて、route:list は「どのファイルの何行目で定義されたか」を教えてくれません。それでも「Go to Route Definition」を実現したかったので、routes/ 配下のPHPファイルを検索する方式にしました。
問題は、ルートグループの prefix や Route::resource() があると、ファイル上の文字列と実際のURIが一致しないことです。たとえば api/posts/{post} というURIは、routes/api.php に Route::apiResource('posts', ...) と書かれているだけです。
そこで検索パターンを確度の高い順に並べました。
-
->name('admin.dashboard')のようにルート名の完全一致 - ルート名の末尾セグメント(
->name('dashboard'))。グループのname('admin.')に対応 - URIの連続する部分列を長い順、同じ長さなら末尾側から。
api/posts/{post}ならapi/posts、posts/{post}、{post}、posts、apiの順
これで動作確認用に用意した25本のルートすべてが、妥当な行に飛ぶようになりました。ただし、この25本は検索方式を作りながら用意したものなので、社内の実プロジェクトでの精度はまだ確認できておらず、外れることもある前提です。そのため「見つからない」と通知を出す設計にして、当たれば便利という位置付けにしています。
6. VSCodeのインストールでハマったこと
プロファイルへのインストール: code --install-extension はオプションなしだとDefaultプロファイルに入ります。検証用に新しいプロファイルを作って試したところアイコンが出ず、原因はこれでした。--profile <名前> を付ける必要があります。
今後
- 実際のプロジェクトで問題なくエンドポイントとソースが紐付くか確認する
- Marketplaceへの公開。現状はGitHub Releaseのvsixからのインストールのみ
まとめ
VSCode拡張は「package.jsonで宣言、extension.tsで登録」という構造さえ掴めば、初めてでも実用的なものが作れました。TreeViewの制約は多いものの、QuickPickやThemeColorなど標準の部品でお手軽にUI作成ができました。
一方で、拡張本体よりも「Laravelが実際に何を返すか」「Docker環境でどう動かすか」といった周辺のほうが手間取りました。興味ある方はぜひ使ってご意見ください。
参考リンク
- Laravel Routes Explorer (GitHub)
- VSCode Extension API: Tree View
- Laravel Routing: Listing Your Routes
- laravel/framework #59237: Display file path and line number for closure routes
お知らせ
技術ブログを週1〜2本更新中、ソーイをフォローして最新記事をチェック!
https://qiita.com/organizations/sewii



