Nuxt.js × Vuetify × TypeScript × ESLint × Prettier × Storybook × Jestを使用して開発環境を構築
[追記]
この記事の情報が古くなったので、新しく記事を書き直してみました。(2020/4/3)
タイトルがごちゃごちゃしてますね...
最近Nuxt.jsを触り始めまして、アプリを作ることにしました。
なんとなく、「今時なものを使用して開発したい!」と思ってこの構成にしました。
いざ開発をしてみると、導入などで色々とハマりまして...😇
やったことを忘れないために、環境構築の内容を紹介しようと思います。
正しい設定ができていない場合もあるかもしれませんので、そこはご了承ください😅
対象となる人
- この構成で開発しようとしている人
ぐらいかなと思います。
なお、今回はyarn
ではなくnpm
を使用して説明していくものとします。
動作確認環境
- Mac10.14.6
- VSCode
- Veturをインストール済み
それではやっていきます💪
プロジェクトを作成
create-nuxt-appコマンドを使用してプロジェクトを作成
コマンドは以下の通り
npx create-nuxt-app <project-name>
以下のコマンドを実行します(プロジェクト名は任意のものに変更しても構いません)
npx create-nuxt-app nuxt-typescript-starter
そうするといくつか質問されますので、
? Choose features to install
は Linter / Formatter
とPrettier
? Use a custom UI framework
は vuetify
? Use a custom test framework
は jest
をそれぞれ選択します。他の質問は任意に回答してOKです。
今回はこのように回答しました。
? Project name nuxt-typescript-starter ? Project description My stellar Nuxt.js project ? Use a custom server framework none ? 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 Single Page App ? Author name ikkyu-3 ? Choose a package manager npm
質問に回答し終わるとプロジェクトが作成されます🔨
作成されたプロジェクトは、以下のようなディレクトリ構成になっています。
. ├── .babelrc ├── .editorconfig ├── .eslintrc.js ├── .git ├── .gitignore ├── .prettierrc ├── README.md ├── assets/ ├── components/ ├── jest.config.js ├── layouts/ ├── middleware/ ├── node_modules/ ├── nuxt.config.js ├── package-lock.json ├── package.json ├── pages/ ├── plugins/ ├── static/ ├── store/ └── test/
プロジェクトができましたので、npm run dev
と入力し実行します。
サーバが起動し、http://localhost:3000/
にアクセスしてページが表示されていることを確認します。
非常に簡単ですね🎉
テストを実行
では次にテストを実行してみます。Ctrl + Cでサーバを停止させたら、テストを実行してみます。
$ npm test > jest PASS test/Logo.spec.js Logo ✓ is a Vue instance (8ms) -----------|----------|----------|----------|----------|-------------------| File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | -----------|----------|----------|----------|----------|-------------------| All files | 0 | 100 | 100 | 0 | | index.vue | 0 | 100 | 100 | 0 | 61,62 | -----------|----------|----------|----------|----------|-------------------| Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 1.562s Ran all test suites.
Jestを使ってテストが実行できました🎉
Lintを実行
次にnpm run lint
を実行してみます。
$ npm run lint > eslint --ext .js,.vue --ignore-path .gitignore .
問題なくなく実行できています。eslintの設定は、プロジェクト作成時にPrettierの連携設定もされています。
とうことで、Nuxt.jsとVuetifyとESLintとPrettierとJestが使用できることを確認できました👏
デフォルトでここまでやってくれるのは、本当に助かります👌
では、ここで一旦使用できるようになったものをまとめます。
- [x] Nuxt.js
- [x] Vuetify
- [x] ESLint
- [x] Prettier
- [ ] TypeScript
- [ ] Storybook
- [x] Jest
次はコミット時にESLintを実行させたいので、その設定を行います。
コミット時にESLintを実行
必要なパッケージをインストール
Huskyとlint-stagedをインストールします。
npm i -D husky lint-staged
packege.jsonに設定を追加
package.jsonにHuxkyとlint-stagedの設定を追加します。
{ ... "husky": { "hooks": { "pre-commit": "lint-staged" } }, "lint-staged": { "*.{js,vue}": [ "npm run lint --fix", "git add" ] } }
これでコミット時にjs, vueのファイルの場合 npm run lint --fix
が強制的に実行され、ソースコードを綺麗に保つことが、できるようになりました。
次はTypeScriptを導入します。
TypeScriptを導入
Nuxt.jsはTypeScriptをサポートしてますので、公式ドキュメントTypeScript サポート - Nuxt.jsを見ながら導入していきます。
必要なパッケージをインストール
npm i -D @nuxt/typescript npm i ts-node
tsconfig.jsonを作成
touch tsconfig.json
tsconfig.json
の中身はnuxt
コマンド実行時にデフォルト値が設定されるので、空ファイルのままで大丈夫です👌
nuxt.config.js => nuxt.config.tsにファイル名を変更
nuxt.config.jsをnuxt.config.tsにリネームし、ファイルを変更します。
import NuxtConfiguration from '@nuxt/config' import colors from 'vuetify/es5/util/colors' const config: NuxtConfiguration = { ... // 中身は変更せずそのままでOK } export default config;
tsconfig.jsonにデフォルト値を設定
先ほど作成したtsconfig.json
にデフォルト値を設定させたいので、nuxt
コマンドを実行します。
npm run dev
tsconfig.json
にデフォルト値が設定されますので、Ctrl + C
で起動したサーバを停止させます。
TypeScriptで.vue
ファイルを読み込めるように設定
typesディレクトリを作成し、shims-vue.d.ts
を作成します。
declare module '*.vue' { import Vue from 'vue' export default Vue }
次にtypesディレクトリの型ファイルを読み込めるようにするため、tsconfig.json
を変更します。
すでにtypes
で指定されていますが、新しいパッケージの型を入れたらその都度types
に追加する必要があるため、削除します。
変わりにtypeRoots
を使用します。1
{ "compilerOptions": { ..., "typeRoots": [ "./node_modules/@types", "./node_modules/@nuxt/vue-app/types", "./types" ] } }
eslintの設定を変更
TypeScriptファイルをLintできるように、パッケージをインストールします。
npm i -D @typescript-eslint/eslint-plugin @typescript-eslint/parser
インストール後、.eslintrc.js
を変更します。
module.exports = { root: true, env: { browser: true, node: true }, parserOptions: { parser: '@typescript-eslint/parser' }, extends: [ '@nuxtjs', 'plugin:nuxt/recommended', 'plugin:prettier/recommended', 'prettier', 'prettier/vue', 'prettier/@typescript-eslint' ], plugins: [ 'prettier', '@typescript-eslint' ], // add your custom rules here rules: { 'no-unused-vars': 'off' } }
vue-property-decoratorの使用
vue-property-decoratorを使用して、Vueコンポーネントをclass構文でかけるようにします。
npm i vue-property-decorator
vueファイルで、TypeScriptを使用できるように変更していきます。
例として、pages/index.vueを変更してます。
Vueファイル内でTypeScriptを使用するには、Scriptタグを<script lang="ts">
と書きます。
<script lang="ts"> import { Component, Vue } from 'vue-property-decorator' import Logo from '../components/Logo.vue' import VuetifyLogo from '../components/VuetifyLogo.vue' @Component({ components: { Logo, VuetifyLogo } }) export default class Index extends Vue {} </script>
変更後、以下のようなエラーが発生する場合があります。
File '/Users/[ユーザ名]/nuxt-typesciprt-starter/components/Logo.vue' is not a module.Vetur(2306)
読み込むファイルに<script>
が定義されていなかったので、Logo.vue
とVuetifyLogo.vue
に<script lang="ts">
を追加します。そうするとエラーが消えるようです🤔
// Logo.vue ... <script lang="ts"> import { Component, Vue } from 'vue-property-decorator' @Component export default class Logo extends Vue {} </script> ...
// VuetifyLogo.vue ... <script lang="ts"> import { Component, Vue } from 'vue-property-decorator' @Component export default class VuetifyLogo extends Vue {} </script> ...
package.jsonを変更
scripts
のlint
とlint-staged
を変更します。
{ "scripts": { "lint": "eslint --ext .js,.ts,.vue --ignore-path .gitignore .", // tsファイルを対象に追加します ... }, ... "lint-staged": { "*.{js,ts,vue}": [ // tsファイルを対象に追加します "npm run lint --fix", "git add" ] } }
jestの設定を変更
ts-jestを使用して、jestでTypeScriptが使用できるようにします。
npm i -D ts-jest @types/jest
jest.config.js
を変更します。
module.exports = { moduleNameMapper: { '^@/(.*)$': '<rootDir>/$1', '^~/(.*)$': '<rootDir>/$1', '^vue$': 'vue/dist/vue.common.js' }, testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$', moduleFileExtensions: ['js', 'vue', 'ts', 'json'], transform: { '^.+\\.js$': 'babel-jest', '.*\\.(vue)$': 'vue-jest', '^.+\\.tsx?$': 'ts-jest' }, collectCoverage: true, collectCoverageFrom: [ '<rootDir>/components/**/*.vue', '<rootDir>/pages/**/*.vue' ] }
test/Logo.spec.js
をtsファイルに変更して、テストを実行します。
$ npm test > jest PASS test/Logo.spec.ts Logo ✓ is a Vue instance (9ms) ------------------|----------|----------|----------|----------|-------------------| File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | ------------------|----------|----------|----------|----------|-------------------| All files | 22.22 | 100 | 100 | 25 | | components | 50 | 100 | 100 | 50 | | Logo.vue | 100 | 100 | 100 | 100 | | VuetifyLogo.vue | 0 | 100 | 100 | 0 | 10,13 | pages | 0 | 100 | 100 | 0 | | index.vue | 0 | 100 | 100 | 0 | 61,62,63,71 | ------------------|----------|----------|----------|----------|-------------------| Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 2.732s Ran all test suites.
できました🎉これでjestでTypeScriptが使えます。
Vuetifyのコンポーネントを使用する時のjestの設定
jestでTypeScriptを使用できるようになりましたが、vuetifyのコンポーネントを使用すると警告が発生します。
例えば<v-btn>
を使用したcomponents/Button.vue
というファイルを作成します。
<template> <v-btn>Button</v-btn> </template> <script lang="ts"> import { Component, Vue } from 'vue-property-decorator' @Component export default class Button extends Vue {} </script>
次にテストファイルtest/Button.spec.ts
を作成します。
import { mount } from '@vue/test-utils' import Button from '@/components/Button.vue' describe('Button.vue', () => { it('snapshot', () => { const wrapper = mount(Button) expect(wrapper.html()).toMatchSnapshot() }) })
テストを実行します。
$ npm run test test/Button.spec.ts > nuxt-typescript-starter@1.0.0 test /Users/toru/workspace/nuxt-typesciprt-starter > jest "test/Button.spec.ts" PASS test/Button.spec.ts Button.vue ✓ snapshot (11ms) › 1 snapshot written. console.error node_modules/vue/dist/vue.common.dev.js:630 [Vue warn]: Unknown custom element: <v-btn> - did you register the component correctly? For recursive components, make sure to provide the "name" option. found in ---> <Button> <Root> ------------------|----------|----------|----------|----------|-------------------| File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | ------------------|----------|----------|----------|----------|-------------------| All files | 18.18 | 100 | 100 | 20 | | components | 33.33 | 100 | 100 | 33.33 | | Button.vue | 100 | 100 | 100 | 100 | | Logo.vue | 0 | 100 | 100 | 0 | 11,14 | VuetifyLogo.vue | 0 | 100 | 100 | 0 | 10,13 | pages | 0 | 100 | 100 | 0 | | index.vue | 0 | 100 | 100 | 0 | 61,62,63,71 | ------------------|----------|----------|----------|----------|-------------------| Snapshot Summary › 1 snapshot written from 1 test suite. Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 1 written, 1 total Time: 1.333s Ran all test suites matching /test\/Button.spec.ts/i.
スナップショットのみなのでテストは通ってますが、どうやらVuetifyのコンポーネントを読み込めてないっぽいです。
Vuetify公式ドキュメントのUnit testing — Vuetify.jsにjestでテストを行う方法が載っていましたが、試してみたら動きませんでした。。。😇
[Feature Request] Avoid importing globally Vuetify in all tests · Issue #4964 · vuetifyjs/vuetifyを参考にしたら動きましたので、その設定を行います。
testディレクトリ
にsetup.js
を作成します。
import Vue from 'vue' import Vuetify from 'vuetify' Vue.use(Vuetify)
次に、jest.config.js
に設定を追加します。
module.exports = { setupFilesAfterEnv: ['./test/setup.js'], // 追加 ... }
再度テストを実行します。
$ npm run test test/Button.spec.ts -- -u > jest "test/Button.spec.ts" "-u" PASS test/Button.spec.ts Button.vue ✓ snapshot (31ms) › 1 snapshot updated. ------------------|----------|----------|----------|----------|-------------------| File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | ------------------|----------|----------|----------|----------|-------------------| All files | 18.18 | 100 | 100 | 20 | | components | 33.33 | 100 | 100 | 33.33 | | Button.vue | 100 | 100 | 100 | 100 | | Logo.vue | 0 | 100 | 100 | 0 | 11,14 | VuetifyLogo.vue | 0 | 100 | 100 | 0 | 10,13 | pages | 0 | 100 | 100 | 0 | | index.vue | 0 | 100 | 100 | 0 | 61,62,63,71 | ------------------|----------|----------|----------|----------|-------------------| Snapshot Summary › 1 snapshot updated from 1 test suite. Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 1 updated, 1 total Time: 1.488s Ran all test suites matching /test\/Button.spec.ts/i.
警告がでなくなりました🎉
※ Vuetifyのコンポーネントを読み込めているので、snapshotを更新する必要があります。
これでVuetifyを使用した、単一コンポーネントファイルのテストも行えるようになりました🎉
ここまでで、使用できるようになったもは、
- [x] Nuxt.js
- [x] Vuetify
- [x] ESLint
- [x] Prettier
- [x] TypeScript
- [ ] Storybook
- [x] Jest
残るはStorybookを導入です。あとちょい😊
Storybookを導入
StorybookでVueを使用する方法は公式のドキュメントにあります。これを見ながら導入していきます。
自動セットアップと手動セットアップがありますが、自動の方が楽なので今回はこちらを使用します。
npx -p @storybook/cli sb init --type vue
実行すると必要なパッケージがインストールされ、.storybook
とstories
というディレクトリが作成されて、その中にいくつかのファイルが作成されます。
また、package.jsonもstorybook
, build-storybook
というscriptsが新たに追加されています。
とりあえずnpm run storybook
を実行してみます。
サーバが起動したらhttp://localhost:6006/
にアクセスします。
表示できました。Ctrl + Cで起動しているサーバを終了させます。
では次に、JavaScriptで書かれたStorybookをTypeScriptに置き換えてみます。
TypeScriptでStorybookをかけるようにする
必要なパッケージをインストール
ts-loader
, fork-ts-checker-webpack-plugin
をインストールします。
npm i -D ts-loader fork-ts-checker-webpack-plugin
.storybookディレクトリ内にwebpack.config.jsを作成
.storybook
にwebpack.config.js
を追加し、TypeScriptでかかれたStorybookを読み込めるようにします。
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin') module.exports = ({ config }) => { config.resolve.extensions.push('.ts') config.module.rules.push({ test: /\.ts$/, exclude: /node_modules/, use: [ { loader: 'ts-loader', options: { appendTsSuffixTo: [/\.vue$/], transpileOnly: true } } ] }) config.plugins.push(new ForkTsCheckerWebpackPlugin()) return config }
StorybookでVuetifyを使用できるようにする
StorybookでVuetifyを使用するため、.storybook/config.js
を以下のように変更します。
import { configure, addDecorator } from '@storybook/vue' import 'vuetify/dist/vuetify.css' import Vue from 'vue' import Vuetify from 'vuetify' Vue.use(Vuetify) addDecorator(() => ({ template: '<v-app><story/></v-app>' })) // automatically import all files ending in *.stories.js const req = require.context('../', true, /\.stories\.ts$/) function loadStories() { req.keys().forEach((filename) => req(filename)) } configure(loadStories, module)
ここまでできたら、StorybookをTypeScriptで書いて表示してみます。
TypeScriptを使用してStorybookを書いてみる
components
配下のLogo.vue
とVuetifyLogo.vue
をStorybookで表示させてみます。
storiesディレクトリ
のindex.stories.js
をindex.stories.ts
にリネームし、以下のように書き換えます。
※ storiesディレクトリ内のMyButton.js, Welcome.jsは使用しないので削除します。
import { storiesOf } from '@storybook/vue' import Logo from '../components/Logo.vue' import VuetifyLogo from '../components/VuetifyLogo.vue' storiesOf('Logo', module).add('to Storybook', () => ({ components: { Logo }, template: '<logo />' })) storiesOf('VuetifyLogo', module).add('to Storybook', () => ({ components: { VuetifyLogo }, template: '<vuetify-logo />' }))
npm run storybook
を実行してStorybookを表示してみます。
表示できました!
Storybookを使用すれば、コンポーネントの開発が楽になります👏
ということで
- [x] Nuxt.js
- [x] Vuetify
- [x] ESLint
- [x] Prettier
- [x] TypeScript
- [x] Storybook
- [x] Jest
すべて利用できるようになりました!!👏
以上です。
長くなりました。。。
これで快適に開発ができるかなと思っています。間違っていたら、都度追記や修正していきます。
今回作成したプロジェクトは、GitHubにあげてあります。参考にどうぞ。