LoginSignup
76
64

More than 5 years have passed since last update.

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

Posted at

東京ラビットハウスのerukitiです。ごきげんよう。

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

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

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

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

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

Nuxt v2

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

Nuxt.js のソースコードは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を参考にしつつ、環境をセットアップしてみましょう。

$ 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

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

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

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

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

まずNuxtBuilderのオブジェクトを初期化します。

const { Nuxt, Builder } = require('..')
  let nuxt, builder
    nuxt = new Nuxt(config())
    builder = new Builder(nuxt)
  • Nuxtクラスはlib/core/nuxt.jsで定義されています。
  • Builderクラスはlib/builder/builder.jsで定義されています。
  • Nuxtと一緒にRendererなども一緒に初期化されています。

次にBuilderbuildメソッドを呼び出してビルド処理します。

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

あとは

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

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

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

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

build

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

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

generateRoutesAndFiles

generateRoutesAndFilesではlib/app下にあるファイルを、loadshtemplate機能を使ってテンプレート展開し、プロジェクトディレクトリの.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.jsserver.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-pluginindex.spa.htmlindex.ssr.html
  • webpack optimizationのruntimeChunkを使ったruntime.js(webpack用ランタイム)
  • pages/ 以下のファイルをコンパイルしたもの(たとえば、pages/index.js
  • Vue.jsのSSRで使われるvue-ssr-client-manifest.json

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

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

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

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

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

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

// 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のウェブサーバー起動

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

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

レンダリング

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

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

if (this.noSSR || spa)

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

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

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

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

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

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で、ここらへんをまとめた本を出します!

ご興味があればサークルページから、チェックリストに登録してみてください。

76
64
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
76
64