Nuxt.jsのbuild&startが何をやってるのかをコードから追ってみる
以前、Herokuで簡易的なNuxt.jsのアプリケーションのSSRを確認するエントリーを書いた。
書いたはいいけど、で...
なんでこれ動いてんの?
デプロイできたやった〜と言いたいところだけど、あまりにも動作がブラックボックスすぎるので、一体何がどうなっているのかを調べてみることにする。
Herokuへのデプロイ
node.jsアプリケーションをHerokuにデプロイする際は、デフォルトではpacakge.jsonに記載のstart
scriptが実行される。
(参考: Deploying Node.js Apps on Heroku | Heroku Dev Center)
そしてさらに、デプロイ時には以下の設定を追加する必要がある。
"heroku-postbuild": "npm run build"
つまりデプロイ時には以下の流れでビルドが実行されていることがわかる。
nuxt build
nuxt start
オフィシャルのガイドによると
nuxt build
- アプリケーションを Webpack でビルドし、JS と CSS をプロダクション向けにミニファイしますnuxt start
- プロダクションモードでサーバーを起動します(nuxt build 後に実行してください)
とのこと。
調べること
というわけで、以下を調べてみることにする。
next build
では何をビルドして何を作っているのかnuxt start
では何を起動していて、どこでSSRをやっているのか。
nuxt
スクリプト
そもそも nuxt
というスクリプトのコードを確認してみる。
nuxt.js/nuxt at dev · nuxt/nuxt.js · GitHub
色々やってるけど、とりあえず
const bin = join(__dirname, 'nuxt-' + cmd)
という箇所があり、cmd
の部分に指定したオプションが渡る。
つまり、nuxt-build
や nuxt-start
といったスクリプトが実行される。
nuxt-build
nuxt.js/nuxt-build at dev · nuxt/nuxt.js · GitHub
オプションのロードなどの後に、以下のようなコードがある。
... if (options.mode !== 'spa') { // Build for SSR app builder.build() .then(() => debug('Building done')) .catch((err) => { console.error(err) process.exit(1) }) } else { ...
options.mode !== 'spa'
という記述があるが、Nuxt.jsではSSRを使わないアプリケーションの構築もサポートしているので、その場合にビルド方法が変わるものと思われる。
https://ja.nuxtjs.org/guide#シングルページアプリケーション-spa-
デフォルトではSSRは有効なので、どうやら builder.build()
がビルドの本体のようだ。
実態は Builder
というクラス。コード的にはこのあたり。
https://github.com/nuxt/nuxt.js/blob/dev/lib/builder/builder.js#L107
処理の中で特に重要そうなものだけピックアップすると
this.nuxt.ready()
this.generateRoutesAndFiles()
this.webpackBuild()
かな〜と思われる。
this.nuxt.ready()
実体は Nuxt#ready
が該当する。
https://github.com/nuxt/nuxt.js/blob/dev/lib/core/nuxt.js#L51
内部ではさらにModuleContainer#ready
やRenderer#ready
を呼び出しており、デフォルトのものに加えて、任意に追加されたミドルウェアやモジュールをロードしている。
this.generateRoutesAndFiles()
webpackでビルドする前のファイルをテンプレートをもとに生成している。
例えば、pages/
の配下にコンポーネントを突っ込むだけで自動的にルーティングが生成されるのは、このあたりで動的にファイルを取得した上でテンプレートファイルからvue-router向けのjsファイルが出力されている。
pages/
に限らず、Nuxt.jsがいい感じにに解決してくれているものの多くがlib/app
配下にテンプレートとして用意されているっぽいので、生成されるファイルの元となるものが見たい場合はここを参照するとよさそう。
https://github.com/nuxt/nuxt.js/tree/dev/lib/app
this.webpackBuild()
みんな大好きwebpackビルド。
lib/builder/webpack
の配下にwebpackビルドで必要なコンフィグファイルがまとまっている。
クライアント側とサーバ側でエントリーファイルが異なるので、client.config.js
と server.config.js
が存在しており、それぞれについてビルドが実行される。
最終的な成果物はデフォルトだとプロジェクトルートから見て .nuxt/dist
配下に出力される。
nuxt-start
nuxt.js/nuxt-start at dev · nuxt/nuxt.js · GitHub
Nuxt#listen
を実行している。
実行前に .nuxt/dist
や、SSRを行う場合には .nuxt/dist/server-bundle.json
の存在をチェックしているので、あらかじめ nuxt build
が実行されていないと落ちる。
Nuxt#listen
https://github.com/nuxt/nuxt.js/blob/dev/lib/core/nuxt.js#L124
this.renderer.app.listen
を実行しており、この app
の実体は connect
のインスタンス。これで実際にNuxt.jsアプリケーションが待ち受け状態になる。
サーバサイドでのルーティングについては、初期化の過程でRenderer#ready
がコールされており、そこからさらに辿ると以下のコードが見つけられる。
// Finally use nuxtMiddleware this.useMiddleware(this.nuxtMiddleware.bind(this))
useMiddleware
は、connect
のインスタンスに対してMiddlewareを登録している。
// Use middleware this.app.use(path, handler)
nuxtMiddleware
については、内部で renderRoute
をコールしており、この中で実際にレンダリングしたHTMLを返却している。
// Call renderToString from the bundleRenderer and generate the HTML (will update the context as well) let APP = await this.bundleRenderer.renderToString(context)
bundleRenderer
にはvue-server-renderer#createBundleRenderer
で生成されたバンドルレンダラがセットされており、.nuxt/dist/server-bundle.json
が元になっている。細かいところまで追えてないが、URLとのマッピングはこのへんで解決しているものと思われる。
というわけで
コードを追ってみることで、事前のビルドで何をしていて、どのあたりでサーバサイドでHTMLを生成しているのかはなんとなくわかった。(ような気がする。)
深く読み込むというよりかは、大体の流れを追うようにコードを見ていったので、もしかすると一部で間違いとかもあるかもしれない。そうだったらスイマセン。
とりあえず、自分の中で完全に闇だった部分が少しわかったのは良かった。
感想としては、コードを見ていると「よくこんなの作ったな...」という気持ちになってきた。OSSコントリビュータに感謝しよう。