Nuxtはどうやってfile-based routingを実現しているか
本記事は Nuxt / UnJS Advent Calendar 2024 の13日目の記事です。
こんにちは。@k0n_karinです。普段からNuxtを使って開発しているのですが、ふとNuxtがどうやってfile-based routingを実現しているのかが気になったので、調べたことを記事に残しておこうと思います。
file-based routingとは
ディレクトリの構造をアプリケーションのルーティングに落とし込むものです。
NuxtやVue(unplugin-vue-router)、Next.jsやRemixなどなど、いろんなフレームワークで活用されています。
- https://nuxt.com/docs/guide/directory-structure/pages
- https://uvr.esm.is/
- https://nextjs.org/docs/app/building-your-application/routing
- https://remix.run/docs/en/main/file-conventions/routes
Nuxtはどうやってpagesディレクトリをvue-routerの設定に落とし込んでいるか
本題のNuxtがどのようにfile-based routingを実現しているかです。気になる点は大きく2つです。
- pagesディレクトリをどうやってvue-routerの設定に変換するか
- vue-routerがいつ・どこで設定されるか(=いつ・どこで
createRouter
が実行されるか)
まず1つ目の鍵を握るのがNuxt Modulesです。
Nuxt Modulesは聞き馴染みが無いかもしれませんが、アプリケーションのビルドタイム(nuxt dev
, nuxt build
)に実行できるモジュールのことです。
tailwindcssやstorybookを使っている方は Nuxt TailwindやNuxt Storybookで馴染みがあるかもしれません。
余談ですが、Nuxt2→Nuxt3の移行のブリッジとして使われたnuxt bridgeも、Nuxt Modulesを使って作られています。それくらい色々できてしまうのがNuxt Modulesです。
Nuxt Modulesはエコシステムが提供してくれるだけでなく、もちろん自分たちのリポジトリに合わせて自作して柔軟に取り回すこともできます。
さて、本題のfile-based routingにおけるNuxt Modulesの登場シーンは、ビルドタイムのpagesディレクトリのスキャンです。
Nuxtはpagesディレクトリを再帰的にスキャンして、vue-routerのための設定ファイルを生成する処理をNuxt Modulesで実行しています。これはpagesディレクトリに関わるいろいろな処理をしているモジュールです。
そして2つ目の鍵はNuxtのプラグインです。
プラグインはおそらくNuxt Modulesより馴染みがある方が多いと思うので、プラグイン自体の詳細は省きます。
Nuxtのプラグインはユーザーが定義するものだけでなく、Nuxt自体がデフォルトで提供しているプラグインもあります。
今回お話するfile-based routingで重要なのはvue-router用のプラグインです。Nuxtは設定なしでvue-routerを使えますが、Nuxt自身が提供するプラグインのおかげです。
この2つを使ってNuxtはfile-based routingを実現しています。
実際の処理フロー
では実際にどのように処理されているかを見ていきましょう。
サンプルリポジトリはこちらです。
ターミナルでnpm run dev
を実行し、Nuxtの開発サーバーを立ち上げるところからざっくりと流れを見ていきます。
NuxtとViteが絡むところは不正確ですが、イメージは大体こんな感じです。
-
npm run dev
でNuxtの開発サーバーの初期化が始まる - Nuxt Modulesの実行が開始
- pages用のNuxt Moduleがpagesディレクトリのスキャンが行い、ルーティングの設定ファイルを生成する
- Nuxtの開発サーバーの初期化が終わる(=
http://localhost:3000
でアプリケーションを開ける) - ブラウザで
http://localhost:3000
を開く - ブラウザ上でNuxtの初期化が始まる
- Nuxtのプラグインの実行が開始
- vue-router用のプラグインが実行され、vue-routerがVueに登録される
- app.vueと任意のpagesコンポーネントが表示される
大事なところ
pages用のNuxt Modulesがpagesディレクトリのスキャンが行い、ルーティングの設定ファイルを生成する
ファイルを生成する処理はこの辺りです。
このaddTemplate
によって生成されるファイルは、JavaScriptでは#build/~
というパスからimport
できます。
実際にブラウザで動作するときは#build
が置換され、http://localhost:3000/_nuxt/@id/virtual:nuxt:{PCのリポジトリパス}/.nuxt
のようなパスになります。
このNuxt Modulesによって生成されるファイルはこんな感じになります。pagesディレクトリの内容がexportされているのがわかります。
試しに、実際にaddTemplate
を使ってみましょう。
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.' } `,
});
},
});
<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
がここです。
次に、vue-routerのcreateRouter
を実行するのはこの辺りです。ここでルーティング設定ファイルを参照していますね。
そして最後にvue-routerをVueに登録して終わりです。
では、ブラウザでも確かめてみましょう。
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
という謎のパスを見かけることがよくありましたが、ようやくその正体がわかってとてもスッキリしました。
Discussion