NuxtJS+TypeScriptの開発記録 (1) 環境構築

## 0. 環境

Windows 10 Pro 1903

```
$ node --version
v12.14.0
$ code --version
1.41.1
26076a4de974ead31f97692a0d32f90d735645c0
x64
```

## 1. NuxtJS ボイラープレート作成

* [NuxtJS](https://ja.nuxtjs.org/)

```
$ npx create-nuxt-app APPNAME
? Project name APPNAME
? Project description SAMPLE
? Author name MYNAME
? Choose the package manager Yarn // お好みで
? Choose UI framework Tailwind CSS // お好みで
? Choose custom server framework None (Recommended) // 今回はSSRなし
? Choose Nuxt.js modules Axios, Progressive Web App (PWA)
? Choose linting tools // あとで自分で入れる
? Choose test framework None // あとで自分で入れる
? Choose rendering mode Single Page App
? Choose development tools
```

## 2. TypeScript対応

* [セットアップ | Nuxt TypeScript](https://typescript.nuxtjs.org/ja/guide/setup.html)の通り

```
$ yarn add -D @nuxt/typescript-build
```

```diff:nuxt.config.js（抜粋）
  /*
   ** Nuxt.js dev-modules
   */
  buildModules: [
+   // Doc: https://typescript.nuxtjs.org/guide/setup.html#installation
+   '@nuxt/typescript-build',
    // Doc: https://github.com/nuxt-community/nuxt-tailwindcss
    '@nuxtjs/tailwindcss'
  ],

```

[セットアップ | Nuxt TypeScript](https://typescript.nuxtjs.org/ja/guide/setup.html)からコピペして、`tsconfig.json` と `vue-shim.d.ts` をルートディレクトリ直下に追加する。

ついでにコンフィグもTSにするために[Runtime（オプション） | Nuxt TypeScript](https://typescript.nuxtjs.org/ja/guide/runtime.html)もやっておく。

```
$ yarn add @nuxt/typescript-runtime
```

```diff:package.json（抜粋）
  "scripts": {
-   "dev": "nuxt",
-   "build": "nuxt build",
-   "start": "nuxt start",
-   "generate": "nuxt generate"
+   "dev": "nuxt-ts",
+   "build": "nuxt-ts build",
+   "start": "nuxt-ts start",
+   "generate": "nuxt-ts generate"
  }
```

`nuxt.config.js`を`nuxt.config.ts`に修正。

```diff:nuxt.config.ts（抜粋）
-export default {
+import { Configuration } from '@nuxt/types';
+
+const nuxtConfig: Configuration = {
   mode: 'spa',
   // 中略
 };
+
+module.exports = nuxtConfig;
```

## 3. Linter と Formatter

Vue用 かつ TypeScript用の Linter & VSCodeで使えるFormatter の設定。

* [ESLint](https://eslint.org/)
* [Prettier](https://prettier.io/)
* [開発ツール - NuxtJS](https://ja.nuxtjs.org/guide/development-tools#eslint-%E3%81%A8-prettier)
* [The future of TypeScript on ESLint - ESLint - Pluggable JavaScript linter](https://eslint.org/blog/2019/01/future-typescript-eslint)
  * [typescript-eslint/README.md at master · typescript-eslint/typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/blob/master/docs/getting-started/linting/README.md)
* [Lint | Nuxt TypeScript](https://typescript.nuxtjs.org/ja/guide/lint.html)

今回はTypeScriptしか使わないので、`babel-eslint` 等のbabel用ツールはいらない。

### 3-1. パッケージのインストール

```
# Vue用のESLintとPrettier
$ yarn add -D eslint eslint-config-prettier eslint-plugin-vue eslint-plugin-prettier prettier
# TypeScript用ESLintプラグイン
$ yarn add -D @typescript-eslint/parser @typescript-eslint/eslint-plugin
# Nuxt.jsのTypeScript用ESLint設定
$ yarn add -D @nuxtjs/eslint-config-typescript
```

```diff:package.json（抜粋）
  "scripts": {
    "dev": "nuxt-ts",
    "build": "nuxt-ts build",
    "start": "nuxt-ts start",
-   "generate": "nuxt-ts generate"
+   "generate": "nuxt-ts generate",
+   "lint": "eslint --ext .ts,.js,.vue ."
  },
+ "prettier": {
+   "singleQuote": true
+ },
```

### 3-2. Linterの設定

```json:.eslintrc.json
{
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:@typescript-eslint/eslint-recommended",

    "plugin:vue/recommended",
    "@nuxtjs/eslint-config-typescript",
    "plugin:prettier/recommended",
    "prettier/@typescript-eslint"
  ],
  "plugins": [
    "@typescript-eslint",
    "vue",
    "prettier"
  ],
  "env": { "browser": true, "node": true, "es6": true },
  "parserOptions": {
    "parser": "@typescript-eslint/parser",
    "sourceType": "module"
  },
  "rules": {
    "prettier/prettier": "error"
  }
}
```

設定ファイルはLinterの対象外にしておいた。

```text:.eslintignore
nuxt.config.ts
tailwind.config.js
```

### 3-3. VSCodeの拡張機能と設定

* [Prettier - Code formatter](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
* [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)

```json:.vscode/settings.json
{
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.formatOnSave": true
}
```

これでファイル保存時に勝手に整形される。

### 3-4. GitのPre-commit Hook

Lintに通らないファイルはコミットできないようにする。

* [Pre-commit Hook · Prettier](https://prettier.io/docs/en/precommit.html)

```
$ npx mrm lint-staged
```

↑のコマンドで自動インストール＆設定ファイル追記までやるらしいんだけど、なんか要らんものまでワサッとインストールされる気がする……。手動でやった方がよかったかもしれない。

手動なら以下をインストールし、`package.json`の変更も手動でやればOK。（というか以前はこうやってた。）

```
$ yarn add -D lint-staged husky
```

```diff:package.json（抜粋）
+  "husky": {
+    "hooks": {
+      "pre-commit": "lint-staged"
+    }
+  },
+  "lint-staged": {
+    "*.{ts,js,vue}": [
+      "eslint --fix",
+      "git add"
+    ]
+  },
```

## 4. テスト（Jest）

[NuxtJS公式にはAVAで記述がある](https://ja.nuxtjs.org/guide/development-tools/#%E3%82%A8%E3%83%B3%E3%83%89%E3%83%84%E3%83%BC%E3%82%A8%E3%83%B3%E3%83%89%E3%83%86%E3%82%B9%E3%83%88)けど、TypeScriptとの相性的には[Jest](https://jestjs.io/ja/)の方がいいような気がする。あとカバレッジとかも全部入りだし……。

* [Jest](https://jestjs.io/ja/)
* [vue-test-utils](https://vue-test-utils.vuejs.org/ja/)
  * [Jest を使用した単一ファイルコンポーネントのテスト](https://vue-test-utils.vuejs.org/ja/guides/testing-single-file-components-with-jest.html)
  * [TypeScript と一緒に使う](https://vue-test-utils.vuejs.org/ja/guides/using-with-typescript.html)
  * 記述がちょっと古く、Vue-CLIをグローバルインストールしろとか書いてあるけど、その辺は無視で……

```
# Jestとvue-test-utils
$ yarn add -D jest @vue/test-utils
# 単一ファイルコンポーネント（.vue）のテスト用
$ yarn add -D vue-jest
# テスト自体をTypeScriptで書く用
$ yarn add -D ts-jest @types/jest
```

```diff:package.json（抜粋）
   "scripts": {
+    "test": "jest",
     // 略
   },
+  "jest": {
+    "moduleFileExtensions": [
+      "ts",
+      "vue",
+      "js"
+    ],
+    "transform": {
+      "^.+\\.ts$": "ts-jest",
+      ".*\\.(vue)$": "vue-jest"
+    },
+    "globals": {
+      "ts-jest": {
+        "tsConfig": "tsconfig.json"
+      }
+    },
+    "moduleNameMapper": {
+      "^@/(.*)$": "<rootDir>/$1"
+    },
+    "collectCoverage": true,
+    "collectCoverageFrom": [
+      "**/*.{ts,vue}",
+      "!**/*.{config,d}.ts",
+      "!**/node_modules/**"
+    ]
+  },
```

```diff:tsconfig.json（抜粋）
    "types": [
      "@types/node",
+     "@types/jest",
      "@nuxt/types"
    ],
```

## 5. 途中で起きたトラブル

### 5-1. &lt;script&gt;のない単一ファイルコンポーネントで、VSCode用拡張Veturがエラー

```
File '.../XXX.vue' is not a module. Vetur(2306)
```

* [Vetur](https://marketplace.visualstudio.com/items?itemName=octref.vetur)

当該拡張機能のリポジトリで報告されている。

* [SFC without a script tag show an error when trying to import · Issue #1187](https://github.com/vuejs/vetur/issues/1187)

[上記issue内のコメント](https://github.com/vuejs/vetur/issues/1187#issuecomment-491123706)にあるが、対策としては空の&lt;script&gt;タグをいれておくしかないらしい。

```html:TSならこうかな
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({});
</script>
```

根本解決ではないが、問題になる場面もあまりないような気もするし、深追いしないことにする。

### 5-2. Jest実行時のエラー

実際に簡単なテストを作って実行してみると、エラーが出た。

```
Cannot find module 'babel-core'
```

TypeScript用に（babelを避けて）色々したからアカンのか？と思ったが、検索すると以下のissueと関連パッケージを見つけた。

* [vue-jest can't find babel · Issue #160 · vuejs/vue-jest](https://github.com/vuejs/vue-jest/issues/160)
* [babel/babel-bridge: A placeholder package that bridges babel-core to @babel/core.](https://github.com/babel/babel-bridge)

これを見る限り、どうも「babel6系までは`"babel-core": "6.x"`だったが、7系からは`"@babel/core": "7.x"`に変わったせいで、名前解決できなくて見つからないと言われる」問題っぽい。

[babel/babel-bridge](https://github.com/babel/babel-bridge)はこの問題を解決するための名前解決用ラッパーパッケージのようだ。

```
$ yarn add -D babel-core@7.0.0-bridge.0
```

これでテストが動作するようになった。