Nuxt.jsのbuild&startが何をやってるのかをコードから追ってみる
以前、Herokuで簡易的なNuxt.jsのアプリケーションのSSRを確認するエントリーを書いた。
http://mugi1.hateblo.jp/entry/2017/12/06/000000
書いたはいいけど、で...
なんでこれ動いてんの?
デプロイできたやった〜と言いたいところだけど、あまりにも動作がブラックボックスすぎるので、一体何がどうなっているのかを調べてみることにする。
Herokuへのデプロイ
node.jsアプリケーションをHerokuにデプロイする際は、デフォルトではpacakge.jsonに記載のstart scriptが実行される。
(参考: Deploying Node.js Apps on Heroku | Heroku Dev Center)
そしてさらに、デプロイ時には以下の設定を追加する必要がある。
"heroku-postbuild": "npm run build"
つまりデプロイ時には以下の流れでビルドが実行されていることがわかる。
nuxt buildnuxt 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アプリケーションが待ち受け状態になる。
https://github.com/senchalabs/connect
サーバサイドでのルーティングについては、初期化の過程で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コントリビュータに感謝しよう。