Nuxt.js v2 の中身を読んで仕組みを理解しよう

[東京ラビットハウス](https://techbookfest.org/event/tbf05/circle/47010003)のerukitiです。ごきげんよう。

Nuxt大人気ですね。ですが皆さん、Nuxtを使っていてもその中身がどうなってるかまではちゃんと調べている方は少ないのではないでしょうか？エンジニアたるもの中身をちゃんと知らずに使うなどとはもってのほかです！

そこで今回の記事はではNuxtの中身を読んで仕組みを解説します。

2018/10/08（月曜祝日）に開催される[技術書典5](https://techbookfest.org/event/tbf05)では、Nuxtの中身を主な題材として、JavaScriptフレームワークの技術と仕組みについて解説する本と、V8を崇める本の2冊を出す予定です。ご興味があれば[サークルページ](https://techbookfest.org/event/tbf05/circle/47010003)から、チェックリストに登録してみてください。

この記事は、本を書く過程で調べ上げたことを元にブログバージョンとしてまとめたものです。

誤りがある、ここがわかりづらい、もっと深く説明がほしいなどの要望があれば、お気軽にコメントや、[@erukiti](https://twitter.com/erukiti)宛てにご意見・ご要望をお願いします。

## Nuxt v2

本記事で参考としているバージョンは、9/20公開の`commit 7a68e1dde17e0ed7eb7598d9c63ca1ac2383b76e` で、`tag: v2.0.0`です。

Nuxt.js のソースコードは[nuxt/nuxt.js](https://github.com/nuxt/nuxt.js)で公開されています。

## ディレクトリ構成

まずは`nuxt.js`のソースコードでのディレクトリ構成です。

| ディレクトリ名 | 内容     |
| -------------- | -------- |
| bin            | CLI実行ファイル|
| build          | Nuxt自体をビルドするためのファイル群 |
| examples       | サンプル |
| dist           | ビルドされたNuxt |
| lib            | ビルド前のソース |
| lib/app        | クライアント向けの lodash templates |
| lib/builder    | Nuxtのbuilder |
| lib/common     | builer, coreなどから参照される共通コード |
| lib/core       | Nuxtのcoreとなるコード群 |
| lib/core/middleware | http server向けミドルウェアのコード |

Nuxt.jsなどにおいてミドルウェアというのは、HTTP server向けに特定のインターフェースを持つ関数のことを指します。

これとは別に、`.nuxt`というディレクトリがアプリケーションのディレクトリ下に作成されます。もちろんnuxt.jsとは違うディレクトリの下にあるはずなのでご注意ください。

| ディレクトリ名      | 内容     |
| ------------------ | -------- |
| .nuxt/             | 中間生成物やビルド成果物などが配置される |
| .nuxt/components | 中間生成物で、Vue component |
| .nuxt/views | 中間生成物で、テンプレートとなるHTMLファイル|
| .nuxt/layouts | 中間生成物でレイアウト用Vueファイル |
| .nuxt/dist     | ビルド成果物 |
| .nuxt/dist/client | クライアントに配信する方のビルド成果物 |
| .nuxt/dist/server | SSR用のビルド成果物 |

`.nuxt`とあれば、アプリケーションのディレクトリ下にある点にご注意ください。

## 今回説明に使うサンプル

[CONTRIBUTING](https://github.com/nuxt/nuxt.js/blob/dev/CONTRIBUTING.md)を参考にしつつ、環境をセットアップしてみましょう。

```
$ git clone nuxt....
$ cd nuxt.js
$ yarn
```

今回はアプリケーションディレクトリを `_/` とします。

```
$ mkdir _
$ cd _
$ mkdir pages
$ cat > pages/index.vue
<template>
  <div>ほげほげ</div>
</template>
```
catでファイルを作る場合、`</template>`の次の行で、CTRL+Dを押してファイル入力を終了します。

```
$ yarn build ; bin/nuxt _
 INFO  Building project

✔ success Builder initialized
✔ success Nuxt files generated

 READY  Listening on http://localhost:3000
```

このコマンドで _/ をアプリケーションディレクトリとして、nuxtが起動します。デフォルトだと、localhostの3000番ポートで起動するので、ウェブブラウザでアクセスしてみましょう。

![nuxt-sample.png](https://qiita-image-store.s3.amazonaws.com/0/3134/4ff658ec-0bda-f868-45a9-fa0353fe9ffb.png)

たった1ファイル`index.vue`を用意するだけでウェブページが表示されました。Nuxtはほんと素晴らしいですね！

## 大まかなNuxtの動きを見てみよう

Nuxtをdevelopment modeで起動し、ウェブブラウザでトップページにアクセスした時の中の挙動を見てみましょう。

起点は`bin/nuxt` および `bin/nuxt-dev`です。bin/下のファイルは、CLIで`nuxt`コマンドを叩いた時に呼ばれるコードです。

まず`Nuxt`と`Builder`のオブジェクトを初期化します。

```js
const { Nuxt, Builder } = require('..')
```

```js
  let nuxt, builder
```

```js
    nuxt = new Nuxt(config())
    builder = new Builder(nuxt)
```

* `Nuxt`クラスは`lib/core/nuxt.js`で定義されています。
* `Builder`クラスは`lib/builder/builder.js`で定義されています。
* `Nuxt`と一緒に`Renderer`なども一緒に初期化されています。

次に`Builder`の`build`メソッドを呼び出してビルド処理します。

```js
      .then(() => builder.build())
```

あとは

```js
      .then(() => nuxt.listen(port, host, socket))
```

あとは`Nuxt`のウェブサーバーを起動します。

```js
    nuxt.hook('watch:fileChanged', (builder, fname) => {
      consola.debug(`[${fname}] changed, Rebuilding the app...`)
      startDev({ nuxt: builder.nuxt, builder })
    })
```

ここらへんのコードではファイル変更を検知して、再起動する処理が入っていたりしますが、そこらへんは今回は省略します。

### build

`Builder`の`build`メソッドでは次の2つの処理を行います。

* `generateRoutesAndFiles`メソッドでテンプレートからバンドル前のファイルを生成する
*  `webpackBuild`メソッドでwebpackを走らせてバンドル処理を行う

### generateRoutesAndFiles

`generateRoutesAndFiles`では`lib/app`下にあるファイルを、`loadsh`の`template`機能を使ってテンプレート展開し、プロジェクトディレクトリの`.nuxt/`以下に書き出します。

|元ファイル|書き出し先|
|----------|----------|
|lib/app/App.js|.nuxt/App.js|
|lib/app/client.js|.nuxt/client.js|
|lib/app/empty.js|.nuxt/empty.js|
|lib/app/index.js|.nuxt/index.js|
|lib/app/middleware.js|.nuxt/middleware.js|
|lib/app/router.js|.nuxt/router.js|
|lib/app/server.js|.nuxt/server.js|
|lib/app/utils.js|.nuxt/utils.js|
|lib/app/components/nuxt-error.vue|.nuxt/components/nuxt-error.vue|
|lib/app/components/nuxt-loading.vue|.nuxt/components/nuxt-loading.vue|
|lib/app/components/nuxt-child.js|.nuxt/components/nuxt-child.js|
|lib/app/components/nuxt.js|.nuxt/components/nuxt.js|
|lib/app/components/no-ssr.js|.nuxt/components/no-ssr.js|
|lib/app/components/nuxt-link.js|.nuxt/components/nuxt-link.js|
|lib/app/layouts/default.vue|.nuxt/layouts/default.vue|
|lib/app/views/loading/default.html|.nuxt/loading.html|
|lib/app/views/app.template.html|.nuxt/views/app.template.html|
|lib/app/views/error.html|.nuxt/views/error.html|

これらのうち、`client.js`と`server.js`は次のwebpackBuild行程でバンドル処理のエントリポイントとして使われます。componentsは、Vue componentとして登録され、views/はHTMLのテンプレート、layoutsは、デフォルトのレイアウトとして利用されます。

### webpackBuild

`webpackBuild`ではclientとserver向けにそれぞれバンドル処理をします。

client向けでは`webpack-hot-middleware/client`と先ほどテンプレート展開された`.nuxt/client.js`をソースとして、いくつかのファイルをはき出します。

バンドル処理では普通ならはき出すファイルは1つになるのですが、webpackの機能・プラグインを使っているため複数のファイルがはき出されます。

* メインファイルである`app.js`
* webpack optimizationのsplitChunkを使った`commons.app.js`
* [html-webpack-plugin](https://github.com/jantimon/html-webpack-plugin)で`index.spa.html`と`index.ssr.html`
* webpack optimizationのruntimeChunkを使った`runtime.js`（webpack用ランタイム）
* pages/ 以下のファイルをコンパイルしたもの（たとえば、`pages/index.js`）
* [Vue.jsのSSR](https://ssr.vuejs.org/ja/guide/build-config.html)で使われる`vue-ssr-client-manifest.json`

色々はき出していますが、基本的にはWebpack4における最適化のためのものです。

Nuxtでこれらの面倒を見ているので、ユーザーは特に何も設定しなくてもwebpack最適化の恩恵を受けられます。素晴らしいですね！

くわしいことは、lib/builder/webpack以下を読み解く必要がありwebpackという大いなる闇の深淵をのぞき込むことになりますので、今回の記事ではおいておきます。

server向けでは、`nuxt/server.js`をソースとして`server-bundle.json`というファイルをはき出していてSSRに使います。

client向けにせよserver向けにせよ、webpackのバンドル処理を行った場合、副次的に`Renderer`の`loadResources`が呼び出されます。`loadResources`ではRendererの処理の為に、`vue-ssr-client-manifest.json` `server-bundler.json` `index.ssr.html` `index.spa.html`のそれぞれのファイルが読み出されレンダラーの初期化が行われます。

ちなみにこれらのファイルはdevだと、webpack-dev-middlewareのせいで、実際のファイルシステムには吐き出されません。実際にファイルを確認したい場合は、[webpack-dev-middleware](https://github.com/webpack/webpack-dev-middleware)の設定を変更して、builder.jsのオプションに、`writeToFile: true`などを指定すると良いでしょう。

```js
// lib/builder/builder.js

    // Create webpack dev middleware
    this.webpackDevMiddleware = pify(
      webpackDevMiddleware(
        compiler,
        Object.assign(
          {
            publicPath: this.options.build.publicPath,
            stats: false,
            logLevel: 'silent',
            watchOptions: this.options.watchers.webpack
          },
          this.options.build.devMiddleware
        )
      )
    )
```

nuxtの設定ファイルをいじるべきではありますが、破壊的コードリーディング（実際にコードを変更したり、`consola.log`などを差し込む）とコードリーディングがはかどるので、筆者は面倒くさがってそのままコードを書き換えてしまいました。

### Nuxtのウェブサーバー起動

`Nuxt`は`lib/core/nuxt.js`で定義されています。`listen`メソッドにより、ウェブサーバーを起動し、いくつかのミドルウェアを登録します。

ここまでで、一通りの初期化が完了したことになります。

### レンダリング

ウェブサーバーにアクセスが来た場合、`lib/core/middleware/nuxt.js`で定義されてるミドルウェア経由で、`Renderer`の`renderRoute`メソッドが呼び出されます。

`renderRouteメソッド`では、SSRモードかSPAモードかで挙動が変わります。

```js
if (this.noSSR || spa)
```

このif文の中身はSPAモードとしての挙動で、if文を抜けたあとの処理がSSRモードの処理になります。

`nuxt dev`で何もいじらずに立ち上げた場合はSSRモードなので、そちらの説明をします。

```js
let APP = await this.bundleRenderer.renderToString(context)
```

`vue-server-renderer`パッケージの`BundleRenderer`を使いHTMLにレンダリングしてAPP変数に納めます。この`this.bundleRenderer`は、`loadResources`メソッドから`createBundleRenderer`により初期化されています。

APPの次は、微妙に涙ぐましいコードでヘッダー情報をHEAD変数に納めます。

```js
const m = context.meta.inject()
let HEAD = m.title.text() + m.meta.text() + m.link.text() + m.style.text() + m.script.text() + m.noscript.text()
if (this.options._routerBaseSpecified) {
  HEAD += `<base href="${this.options.router.base}">`
}

if (this.options.render.resourceHints) {
  HEAD += context.renderResourceHints()
}
```

あとは、同じ`Renderer`クラスの`renderTemplate`メソッドを呼び出し、`index.ssr.html`に、APPとHEADを当てはめてHTMLファイルが完成します。

HTMLファイルからはいくつかのJSファイルが読み込まれることにはなります。

* /_nuxt/runtime.js
* /_nuxt/pages/index.js
* /_nuxt/commons.app.js
* /_nuxt/vendor.app.js
* /_nuxt/app.js

これらのファイル名に見覚えがあることでしょう。Builderがwebpackで生成したファイルです。

ここまでで、Nuxtのdevelopmentモードでの基本的な処理の流れを説明しました。

## まとめ

* Builderで、lodashのtemplate展開とwebpackのbundle処理を行う
* Nuxt middlewareなどを登録したウェブサーバーを起動する
* ウェブへアクセスが来たら、RendererによりSSRして結果となるHTMLを返す

Nuxtがやっていることを大雑把に説明するとこんな感じです。簡単ですね！

### 技術書典5で、ここらへんをまとめた本を出します！

ご興味があれば[サークルページ](https://techbookfest.org/event/tbf05/circle/47010003)から、チェックリストに登録してみてください。