🛣️

Nuxtはどうやってfile-based routingを実現しているか

2024/12/23に公開

本記事は Nuxt / UnJS Advent Calendar 2024 の13日目の記事です。

こんにちは。@k0n_karinです。普段からNuxtを使って開発しているのですが、ふとNuxtがどうやってfile-based routingを実現しているのかが気になったので、調べたことを記事に残しておこうと思います。

file-based routingとは

ディレクトリの構造をアプリケーションのルーティングに落とし込むものです。

NuxtやVue(unplugin-vue-router)、Next.jsやRemixなどなど、いろんなフレームワークで活用されています。

Nuxtはどうやってpagesディレクトリをvue-routerの設定に落とし込んでいるか

本題のNuxtがどのようにfile-based routingを実現しているかです。気になる点は大きく2つです。

  • pagesディレクトリをどうやってvue-routerの設定に変換するか
  • vue-routerがいつ・どこで設定されるか(=いつ・どこでcreateRouterが実行されるか)

まず1つ目の鍵を握るのがNuxt Modulesです。

Nuxt Modulesは聞き馴染みが無いかもしれませんが、アプリケーションのビルドタイム(nuxt dev, nuxt build)に実行できるモジュールのことです。

https://nuxt.com/docs/guide/concepts/modules

tailwindcssやstorybookを使っている方は Nuxt TailwindNuxt Storybookで馴染みがあるかもしれません。
余談ですが、Nuxt2→Nuxt3の移行のブリッジとして使われたnuxt bridgeも、Nuxt Modulesを使って作られています。それくらい色々できてしまうのがNuxt Modulesです。
Nuxt Modulesはエコシステムが提供してくれるだけでなく、もちろん自分たちのリポジトリに合わせて自作して柔軟に取り回すこともできます。

さて、本題のfile-based routingにおけるNuxt Modulesの登場シーンは、ビルドタイムのpagesディレクトリのスキャンです。

Nuxtはpagesディレクトリを再帰的にスキャンして、vue-routerのための設定ファイルを生成する処理をNuxt Modulesで実行しています。これはpagesディレクトリに関わるいろいろな処理をしているモジュールです。

https://github.com/nuxt/nuxt/blob/1c418d0ea3b046f2f7dedc3f39a87cc5be3e90b2/packages/nuxt/src/pages/module.ts

そして2つ目の鍵はNuxtのプラグインです。
プラグインはおそらくNuxt Modulesより馴染みがある方が多いと思うので、プラグイン自体の詳細は省きます。

Nuxtのプラグインはユーザーが定義するものだけでなく、Nuxt自体がデフォルトで提供しているプラグインもあります。
今回お話するfile-based routingで重要なのはvue-router用のプラグインです。Nuxtは設定なしでvue-routerを使えますが、Nuxt自身が提供するプラグインのおかげです。

https://github.com/nuxt/nuxt/blob/4d22f4d5ace0e206c90c7e3d334a1a2394f4ecbc/packages/nuxt/src/pages/runtime/plugins/router.ts

この2つを使ってNuxtはfile-based routingを実現しています。

実際の処理フロー

では実際にどのように処理されているかを見ていきましょう。

サンプルリポジトリはこちらです。

https://github.com/konkarin/nuxt-file-based-routing

ターミナルでnpm run devを実行し、Nuxtの開発サーバーを立ち上げるところからざっくりと流れを見ていきます。

NuxtとViteが絡むところは不正確ですが、イメージは大体こんな感じです。

  1. npm run devでNuxtの開発サーバーの初期化が始まる
  2. Nuxt Modulesの実行が開始
  3. pages用のNuxt Moduleがpagesディレクトリのスキャンが行い、ルーティングの設定ファイルを生成する
  4. Nuxtの開発サーバーの初期化が終わる(=http://localhost:3000でアプリケーションを開ける)
  5. ブラウザでhttp://localhost:3000を開く
  6. ブラウザ上でNuxtの初期化が始まる
  7. Nuxtのプラグインの実行が開始
  8. vue-router用のプラグインが実行され、vue-routerがVueに登録される
  9. app.vueと任意のpagesコンポーネントが表示される

大事なところ

pages用のNuxt Modulesがpagesディレクトリのスキャンが行い、ルーティングの設定ファイルを生成する

ファイルを生成する処理はこの辺りです。

https://github.com/nuxt/nuxt/blob/1c418d0ea3b046f2f7dedc3f39a87cc5be3e90b2/packages/nuxt/src/pages/module.ts#L499-L510

このaddTemplateによって生成されるファイルは、JavaScriptでは#build/~というパスからimportできます。
実際にブラウザで動作するときは#buildが置換され、http://localhost:3000/_nuxt/@id/virtual:nuxt:{PCのリポジトリパス}/.nuxt のようなパスになります。

このNuxt Modulesによって生成されるファイルはこんな感じになります。pagesディレクトリの内容がexportされているのがわかります。

試しに、実際にaddTemplateを使ってみましょう。

modules/sample.ts
import { addTemplate, defineNuxtModule } from "@nuxt/kit";

export default defineNuxtModule({
  setup() {
    addTemplate({
      filename: "sample.ts",
      getContents: () =>
        `export default function () { return 'this is a sample from nuxt modules.' } `,
    });
  },
});
pages/module.vue
<script setup lang="ts">
// @ts-expect-error virtual file
import sample from "#build/sample"; // created by modules/sample.ts
</script>

<template>
  <h1>{{ sample() }}</h1>
</template>

これでhttp://localhost:3000/moduleを開くと、画面に”this is a sample from nuxt modules.”が表示されます。

開発者ツールでpages/module.vueを見ましょう。.vueファイルはコンパイル前、コンパイル後、バンドラ用の3種類がありますが、コンパイル後を見ましょう。これが実際にブラウザ上で動作するJavaScriptです。

そうすると、import sample from "#build/sample"のパスが書き換わっていることがわかります。

これで、addTemplateの動きを確認できたので、Nuxt Modulesによるルーティングの設定ファイル生成まで理解できました。

vue-router用のプラグインが実行され、vue-routerがVueに登録される

このプラグインは、addTemplateで生成されたルーティング設定ファイルに基づいてvue-routerのルーティングを設定します。

まず、生成したルーティング設定ファイルのimportがここです。

https://github.com/nuxt/nuxt/blob/4d22f4d5ace0e206c90c7e3d334a1a2394f4ecbc/packages/nuxt/src/pages/runtime/plugins/router.ts#L20-L21

次に、vue-routerのcreateRouterを実行するのはこの辺りです。ここでルーティング設定ファイルを参照していますね。

https://github.com/nuxt/nuxt/blob/4d22f4d5ace0e206c90c7e3d334a1a2394f4ecbc/packages/nuxt/src/pages/runtime/plugins/router.ts#L64-L89

そして最後にvue-routerをVueに登録して終わりです。

https://github.com/nuxt/nuxt/blob/4d22f4d5ace0e206c90c7e3d334a1a2394f4ecbc/packages/nuxt/src/pages/runtime/plugins/router.ts#L96

では、ブラウザでも確かめてみましょう。

vue-routerのプラグインはブラウザ上ではhttp://localhost:3000/_nuxt/node_modules/nuxt/dist/pages/runtime/plugins/router.jsで見れます。import _routes from "#build/routes"のパスが書き換わっていることがわかります。

ちなみに、実行されるプラグインの一覧はhttp://localhost:3000/_nuxt/@id/virtual:nuxt:/{PCのリポジトリパス}/.nuxt/plugins.client.mjs からすべて確認できます。このファイルもNuxt Modulesで生成されます。

ここまでで、Nuxtのfile-based routingの概要を掴むことができました!

余談:どうやってこの記事の内容を調べたか

大半はVSCodeとChrome開発者ツールのデバッガで処理を追いました。ひたすらブレークポイントを貼ってコールスタックを追って調べています。

https://sosukesuzuki.dev/posts/jsc-contributions-2024/ を読んで、著名なコントリビューターの方でもプリントデバッグやデバッガで処理を追うことを知って、言語と対象は全然違いますが、自分も同じだ〜と思いました。もっといい方法があれば知りたいところです。

あとはNuxtのリポジトリをローカルマシンに落としてTypeScriptのファイルを眺めたり、Copilotに教えてもらったりしました。

デバッグだけだとnode_modulesの型がないJavaScriptを見ることになるので、型を見れるのはやはりいいですね。

終わりに

vue-routerの設定無しでvue-routerが設定される仕組みが気になって調べてみたら、思った以上のボリュームになりました。この記事を書くまでNuxt Moduleをあまり知りませんでしたが、こういう事ができるのか〜と学びが多かったです。

Nuxt2からNuxt3に移行してから、開発中に度々#buildという謎のパスを見かけることがよくありましたが、ようやくその正体がわかってとてもスッキリしました。

Vue・Nuxt 情報が集まる広場 / Plaza for Vue・Nuxt.

Discussion