Nuxt はずっと前から気にはなってて、でも「今回は SSR しないからいいや」とか「今回は静的サイトジェネレーターで作るからいいや」とか思って避けてきたけど、なんか最近 Nuxt 使うって話を 3 回くらい聞いたので、ちょっと使ってみて構成とか眺めてみようと思う
先に使ってみた感想を述べておくと
- Nuxt の公式ドキュメントがちゃんと作られているので、これだけ見れば大体解決した
- 後述してますが TypeScript とか Vuex とかとても簡単に導入できるので感動した
- webpack 周りが隠蔽されてる
- 前に VueCLI v2 使ってみた時はけっこうビルド周りがむき出しになってた気がする(VueCLI v3 ではこの辺解決されてるっぽい)
- Vue で SSR する時に Nuxt を使うという印象を持っていたが、SSR モードか SPA モードか選べたので考えが違った
- フロントエンドチームが多いチームであれば、Angular などのガッチリ系フレームワークも検討しようと思っていたが、Vue が好きなら Nuxt でもいいかもしれない
- TypeScript のサポートも昔よりはかなり良くなってて、Vue v3 がリリース(今年リリースされる?)されたら Vue 自体が TypeScript で作り直されるので、さらに Vue + Nuxt + TypeScript 環境が良くなっていくのではないかと思う
今回作業したリポジトリは以下
https://github.com/kurosame/event-search
インストール
create-nuxt-appを使うと良さそう
npx でいけるとかすばらしい
npx create-nuxt-app event-search
プロジェクト名は GitHub のリポジトリ名にした
? Project name event-search
? Project description My groundbreaking Nuxt.js project
? Use a custom server framework express
? Choose features to install Progressive Web App (PWA) Support, Linter / Formatter, Prettier, Axios
? Use a custom UI framework vuetify
? Use a custom test framework jest
? Choose rendering mode Universal
? Author name kurosame
? Choose a package manager yarn
とりあえず全部入れといたが、以下に該当する場合はインストールから除外してもいいと思う
(でも環境を CLI ツールで管理する時点で環境周りをメンテナンスすることは基本的に無いと思うので、全部入れてしまっても問題無いと思う)
- SSR しない
? Use a custom server framework
None
? Choose rendering mode
SPA
- モバイル対応しない
? Choose features to install
Progressive Web App (PWA) Supportを選択しない
- HTTP リクエストしない(ほぼ無いと思うが)
? Choose features to install
Axiosを選択しない
このAxiosとは、axiosを拡張したaxios-moduleのことである
- デザインは全部自前
? Use a custom UI framework
None
サーバーの Node フレームワークはあまり詳しくないが、特に今使っているのが無ければ Express か Koa あたりを選んでおけば良いと思う
Vuetify は 1 年くらい使ってるが、かなり完成度高い UI フレームワークと思っているので、入れた
Linter や Prettier や Jest などのテストフレームワークは必須で入れておいた方が良い
実行
package.json を開くと、いくつかスクリプトが設定してあるが、とりあえず以下を実行
yarn dev
2 回目以降はnode_modules/.cache
の下にbabel-loader
のキャッシュを作ってる影響からか、少し速くなっている
でもホットリローディングをサポートしているので、1 回起動すれば遅さは気にならない
nodemonを使って./server
ディレクトリ内のファイルを監視してサーバの再起動を行っている
画面が表示されたら HTML ソースを見てみると、server-rendered="true"
及び HTML の各要素がそのままレンダリングされていると思う
ちゃんと SSR されていることが分かる
ちなみに SPA モードであれば、JS が実行されてから画面がレンダリングされるので、読み込む JS ファイルのみが指定してあり、HTML はスタイルなどを除けばほとんど空である
また、実行すると.nuxt
というディレクトリができており、サーバを立ち上げた時に読み込ませるコンテンツが格納されている
./server/index.js
を見てみると、Nuxt のインスタンスを作成して、Promise を返すnuxt.render
を Express に Middleware としてセットしている
しばらく開発で使うのは、dev スクリプトだけで良さそう
以下の他のスクリプトはある程度 Nuxt でアプリケーション作った後にちゃんと使ってみようと思う
- build
- minify して
.nuxt/dist/
に格納
- minify して
- start
- production モードでサーバを立ち上げる
- generate
- Vue を静的コンテンツとしてビルドして
dist/
に格納してくれる - Netlify や Firebase などでそのままホスティングできる(はず)
- VuePress 使ったことがあれば、それ
- Vue を静的コンテンツとしてビルドして
使ってみる
ちょっとだけ使ってみてやったことや思ったことを書いてみようと思う
TypeScript を使う
yarn add -D @nuxt/typescript ts-node
tsconfig.json
があればnuxt
コマンドで勝手にデフォルト値を書いてくれるらしい
touch tsconfig.json
npx nuxt
先程インストールしたts-node
は Node で TS をトランスパイルせずに実行できるツールでこれが無いとnpx nuxt
が動かない
これで導入は完了
後は、拡張子を.js
から.ts
に変えて中身を書き換えていく
以下はserver/index.js
を TS に修正している例
import NuxtConfiguration from '@nuxt/config'
...
// Import and Set Nuxt.js options
const config: NuxtConfiguration = require('../nuxt.config.js')
config.dev = !(process.env.NODE_ENV === 'production')
...
TS 導入前は上記のconfig.dev
が any 型だったが、導入後はちゃんと型定義の boolean 型を参照している
package.json も忘れずに修正
"scripts": {
- "dev": "cross-env NODE_ENV=development nodemon server/index.js --watch server",
+ "dev": "cross-env NODE_ENV=development nodemon server/index.ts --watch server",
- "start": "cross-env NODE_ENV=production node server/index.js",
+ "start": "cross-env NODE_ENV=production ts-node server/index.ts",
}
Vue コンポーネントの TS 化についてはドキュメントではvue-property-decorator
の使用を薦めている
yarn add vue-property-decorator
layouts/default.vue
を例に修正すると
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
@Component
class Default extends Vue {
clipped: boolean = false
drawer: boolean = false
fixed: boolean = false
items: { icon: string; title: string; to: string }[] = [
{
icon: 'apps',
title: 'Welcome',
to: '/'
},
{
icon: 'bubble_chart',
title: 'Inspire',
to: '/inspire'
}
]
miniVariant: boolean = false
right: boolean = true
rightDrawer: boolean = false
title: string = 'Vuetify.js'
}
export default Default
</script>
ちなみにvue-property-decorator
を使わなくても書ける
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
data(): {
clipped: boolean
drawer: boolean
fixed: boolean
...
} {
return {
clipped: false,
drawer: false,
fixed: false,
...
}
}
})
</script>
Vuex を使う
Nuxt をインストールした時にルート直下に store ディレクトリができており、既に Vuex 入ってる感があるが、中身は空である
Nuxt は store ディレクトリが存在すれば Vuex をインポートしてくれて、store をルートインスタンスに流してくれるらしい
なので
- Vuex を手動で入れる必要がない
- どのコンポーネントからでも
this.$store
で参照できる
また、モジュールモードとクラシックモードの 2 つのモードがあるが、クラシックモードは廃止予定らしい
肥大化してくるとモジュールごとに State が参照できた方が良いので、モジュールモードで良さそう
Nuxt 側でモジュールモードとクラシックモードのどちらで Vuex インスタンスが作られるかの判定は
store/index.(js|ts)
が存在して、Store インスタンスをエクスポートしていれば、クラシックモード
それ以外はモジュールモード
モジュールモードでstore/index.(js|ts)
という名前で作った場合、ルートモジュールという扱いで Store インスタンスの直下に State が存在する
store/モジュール名.(js|ts)
という名前で作った場合、名前付きモジュールとして Store インスタンスの modules プロパティ配下に State が存在する
それぞれの State を参照する場合
- ルートモジュールは
this.$store.state.counter
で参照できる - ルート以外は
this.$store.state.todos.list
で参照できる- 上記は
store/todos.(js|ts)
というファイルを作った場合
- 上記は
ちなみに私は上記のルートモジュールと呼ばれてる Store インスタンスの直下に State を置くのは 1 度もやったことない
以下のファイルを store ディレクトリ配下に追加
interface IState {
counter: number
}
export const state: IState = {
counter: 123
}
簡単すぎる例だが、ファイルを追加するだけで Store に counter が保持できる
そして以下のように参照する
<script>
export default {
mounted() {
console.log(this.$store.state.sample.counter) // 123
}
}
</script>
また、Vuex のヘルパー関数も使える
<script>
import { mapState } from 'vuex'
export default {
computed: {
...mapState({
sample: 'sample'
})
},
mounted() {
console.log(this.sample.counter) // 123
}
}
</script>
Linter とコードフォーマッターを使う
TS を使っているが、TSLint は非推奨となるらしいので、ESLint の TS パーサーを使う
.eslintrc.js
を見る限り ES(JS)だけの Linter で良ければ、Nuxt をインストールした時点で出来てるので、後は rules や extends でルールを追加するだけで良さそう
package.json
を見るとeslint-plugin-prettier
とeslint-config-prettier
が依存パッケージとしてインストールされており、.eslintrc.js
の extends に prettier が設定してあるので、ESLint と Prettier の競合は気にしなくて良さそう
TS 用にLinterを使う場合の修正箇所は以下
yarn add -D @typescript-eslint/eslint-plugin
parserOptions: {
- parser: 'babel-eslint'
+ parser: '@typescript-eslint/parser'
},
- plugins: ['prettier'],
+ plugins: ['@typescript-eslint', 'prettier'],
"scripts": {
- "lint": "eslint --ext .js,.vue --ignore-path .gitignore .",
+ "lint": "eslint --ext .ts,.js,.vue --ignore-path .gitignore .",
}
また、ESLint のルールだと以下のような import は誤検知して、「no-unused-vars」を出してしまう
import NuxtConfiguration from '@nuxt/config'
この場合、以下のように ESLint のルールは握りつぶして、@typescript-eslint
の方のルールを有効化する必要があるらしい
こちらの記事に書いてありますが、いくつかこのような誤検知するルールがあるらしいので、その都度同様の対応が必要
rules: {
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'error'
}
Airbnb の ESLint ルールを入れてみる
yarn add -D eslint-config-airbnb-base
extends: [
'airbnb-base',
...
]
いい感じに動いているが、Vue とか Vuex は Nuxt に直接依存しており、こちらでpackage.json
に書く必要が無いため、以下のような Lint エラーが出てる
.../event-search/pages/index.vue
63:1 error 'vuex' should be listed in the project's dependencies. Run 'npm i -S vuex' to add it import/no-extraneous-dependencies
.../event-search/plugins/vuetify.js
1:1 error 'vue' should be listed in the project's dependencies. Run 'npm i -S vue' to add it import/no-extraneous-dependencies
上記は以下のように対応可能
settings: {
'import/core-modules': ['vue', 'vuex']
},
他にも Nuxt と ESLint で競合するルールはありそう
vue-router を使う
Nuxt ではエントリーポイントとなる JS などで routes を書いてnew VueRouter
をやる必要はない
pages ディレクトリ配下にルーターから直接呼ばれるコンポーネントを配置するだけで良い
以下の 2 つを配置
<template>
<span>サンプルインデックス!</span>
</template>
<template>
<span>サンプルテスト!</span>
</template>
それぞれ以下のようにしてルーティングさせる
<nuxt-link to="/sample">Go sample index</nuxt-link>
<nuxt-link to="/sample/test">Go sample test</nuxt-link>
ファイル名がindex.vue
であれば、ディレクトリ名
が path になり、それ以外のファイル名であれば、ディレクトリ名/ファイル名
が path になる
先程の Vuex とルールが似ている
パラメータ付きの動的ルーティングもファイル名もしくはディレクトリ名の先頭にアンダースコアを付けることで実現できるらしい
nuxt-link というコンポーネントは router-link を extends したコンポーネントとなっており、nuxt-link を使わずに今まで通り<router-link>
を使って実装することも可能
ただし、nuxt-link を使うと画面表示領域にリンクがあれば、そのリンク先のコンテンツを先読みしてくれるので、遷移が速くなる
試しに、以下のコードを適当な Vue の下の方に配置して、ファーストビューから見えないようにして、スクロールしてリンクを表示させるとindex.vue
だけプリフェッチされることが分かる
Chrome DevTool の Network タブで確認すると分かりやすい
<nuxt-link to="/sample">Go sample index</nuxt-link>
<router-link to="/sample/test">Go sample test</router-link>
その他
選択した UI フレームワークでサンプルコードが作られている
私は Vuetify を選んだのだが、割としっかりサンプルコードが Vuetify で作られてて、全部の UI フレームワーク分作られてると思うとちゃんとしてるなって思った
テストのサンプルがほぼ無い
Logo コンポーネントの 1 ケースしかテストのサンプルコードが無い
E2E のサンプルコードは無い
でも最近は Vue のテスト周りのエコシステムが充実してきて、ドキュメントも豊富なので、問題ないと思う
Nuxt のアップデートは簡単にできるのだろうか
Angular みたいなマイグレーション機能はたぶん無いと思うが、Nuxt とか CLI 系のツールを使っているとアップデートが楽にできるとかなりありがたい
簡単にネットで調べた限りは Nuxt を最新にして出たエラーを潰してるっぽい
さいごに
主にツールの導入になってしまい、まだコードはほとんど書いてないのですが、これから ToDo アプリ程度のものは作ってみて、Nuxt を学んでみようかと思います
続きは別記事でまた書こうかなと思います
フロントエンドは全部 1 から作る派なのですが、たまに自動で環境作ってくれる系ツールを使うとそのお手本のような構成に勉強になることがあります
また、今後フロントエンドの環境を作る上で活かせることもあると思うので、試しに使ってみるのもありかなと思いました