3分でつくる2019年版 Nuxt.js TypeScript 開発環境設定

ほとんど自分のためのメモに近いのですが、3分でNuxt.js+TypeScriptの開発環境を整える覚書です。

--- [2019/08/22 追記] ---
※ Nuxt 2.8での設定方法です！
Nuxt 2.9 から Nuxt TypeScript に変わりました！
公式のマイグレーション方法はこちら https://typescript.nuxtjs.org/migration.html

https://typescript.nuxtjs.org/
- - - - - 

--- [2019/09/05 追記] ---
Nuxt 2.9 仕様に記事の内容を変更しました！

------------------------

## Create Nuxt App でベースはセットアップ
```
create-nuxt-app my-app
```
`prettier` と `eslint` を設定する


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

個人的にNuxtのソースファイルは`src`ディレクトリに入れるのが好きなので、
`assets`, `components`, `layouts`, `middleware`, `pages`, `plugins`, `static`, `store` のディレクトリは `src` に移動。

`nuxt.config` に `srcDir: 'src'` を追加。

`nuxt.config.js` を `nuxt.config.ts` に変更。

```nuxt.config.ts
import { Configuration } from '@nuxt/types'

const config: Configuration = {
  // Config設定の記述
  // ...
  buildModules: [
    // Doc: https://github.com/nuxt-community/eslint-module
    '@nuxtjs/eslint-module',
    '@nuxt/typescript-build' // <- 追加
  ],
  // ...
}

export default config
```

tsconfig.jsonを作成

```tsconfig.json
{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "moduleResolution": "node",
    "lib": [
      "esnext",
      "esnext.asynciterable",
      "dom"
    ],
    "esModuleInterop": true,
    "experimentalDecorators": true,
    "allowJs": true,
    "sourceMap": true,
    "strict": true,
    "noImplicitAny": false,
    "noEmit": true,
    "baseUrl": "./src",
    "paths": {
      "~/*": ["./*"],
      "@/*": ["./*"]
    },
    "typeRoots": ["/src/@types"],
    "types": [
      "@types/node",
      "@nuxt/types",
      "@nuxtjs/axios"
    ]
  },
  "exclude": [
    "node_modules"
  ]
}
```

### ESLintの設定
```
yarn add -D @nuxtjs/eslint-config-typescript
```

.eslintrc.jsをTypeScript用に書き換える
ルールはお好みで

```.eslintrc.js
module.exports = {
  root: true,
  parser: 'vue-eslint-parser',
  env: {
    browser: true,
    node: true
  },
  parserOptions: {
    // parser: '@typescript-eslint/parser'
  },
  extends: [
    '@nuxtjs',
    '@nuxtjs/eslint-config-typescript', // <= これを追加
    'plugin:nuxt/recommended',
    'plugin:prettier/recommended',
    'prettier',
    'prettier/vue'
  ],
  plugins: [
    'prettier'
  ],
  // add your custom rules here
  rules: {
    'no-unused-vars': 'off',
    '@typescript-eslint/no-unused-vars': 'error',
    'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
    "vue/singleline-html-element-content-newline": "off",
    "vue/no-v-html": "off",
    "vue/html-closing-bracket-spacing": "off",
    "vue/max-attributes-per-line": [2, {
      "singleline": 5,
      "multiline": {
        "max": 1,
        "allowFirstLine": false
      }
    }]
  }
}
```

### nuxt-ts に変更

NPM Scripts の `nuxt` を `nuxt-ts` に変更する

```package.json
{
  "scripts": {
    "dev": "nuxt-ts",
    "build": "nuxt-ts build",
    "generate": "nuxt-ts generate",
    "start": "nuxt-ts start",
    "lint": "eslint --ext .ts,.js,.vue --ignore-path .gitignore ."
  },
  "dependencies": {
    "@nuxt/typescript-runtime",
    "nuxt"
  },
  "devDependencies": {
    "@nuxt/typescript-build"
  }
}
```

## Vue.extend での開発
jsのときとほとんど同じような記述ができる

```sample.vue
<script lang="ts">
import Vue, { PropType } from 'vue'
import Hoge from '~/components/Hoge.vue'

// Taskの型定義
type Task = {
  id: number
  name: string
  check: boolean
}

// Dataの型定義
type Data = {
  tasks: Task[]
  name: string
}

export default Vue.extend({
  components: {
    Hoge
  },
  props: {
    dataTask: {
      // ArrayやObjectの場合、詳細な型定義はPropTypeを使う
      type: Array as PropType<Task[]>,
      required: true
    }
  },
  data(): Data {
    return {
      tasks: this.dataTask
      name: 'サンプル'
    }
  }
})
</script>
```

## クラスコンポーネントでの開発

```
yarn add nuxt-property-decorator
```

`nuxt-property-decorator` には `vue-class-component`, `vue-property-decorator`, `vuex-class` がラップされている

https://github.com/nuxt-community/nuxt-property-decorator#readme

`nuxt.config`に追加

```
  build: {
    babel: {
      plugins: [
        ["@babel/plugin-proposal-decorators", { legacy: true }],
        ["@babel/plugin-proposal-class-properties", { loose: true }]
      ]
    }
  }
```


## VSCode
VSCodeの設定で、保存時にエラーをチェックして整形してくれるようにする


```/.vscode/settings.json
{
  "javascript.format.insertSpaceBeforeFunctionParenthesis": true,
  "typescript.format.insertSpaceBeforeFunctionParenthesis": true,
  "eslint.enable": true,
  "editor.formatOnSave": false,
  "eslint.run": "onType",
  "vetur.format.defaultFormatter.js": "prettier",
  "vetur.format.defaultFormatter.css": "prettier",
  "vetur.format.defaultFormatter.less": "prettier",
  "vetur.format.defaultFormatter.postcss": "prettier",
  "vetur.format.defaultFormatter.scss": "prettier",
  "vetur.format.defaultFormatter.stylus": "stylus-supremacy",
  "vetur.format.defaultFormatter.ts": "prettier",
  "vetur.validation.style": true,
  "vetur.validation.template": true,
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  }
}
```

package.jsonに追加

```package.json
...
  "browserslist": [
    "last 2 version",
    "android >= 5",
    "IE 11"
  ],
  "_moduleAliases": {
    "~/*": "./src/*",
    "@/*": "./src/*"
  }
...
```

tsconfig.jsonの編集

```tsconfig.json
"baseUrl": "./src"
```

## 型定義
`@types` ディレクトリを作成
`*.d.ts` の形式のファイルを入れる

例）markdownをimportする際に必要 `md.d.ts`

```md.d.ts
declare module '*.md' {
  const content: string
  export default content
}
```
assets から 画像ファイルを import する場合、以下のような型定義を読み込んでおく

```files.d.ts
declare module '*.png'
declare module '*.jpg'
declare module '*.gif'
declare module '*webp'
```

## Nuxt 2.9から config に router の設定を書くのが非推奨になった
nuxt.config ではなく、/app/router.scrollBehavior.js を作成してそこに設定を記述する。
https://nuxtjs.org/api/configuration-router/#scrollbehavior

```srcDirディレクトリ/app/router.scrollBehavior.js
// ページ遷移時にスクロールトップ
export default function(to, from, savedPosition) {
  return { x: 0, y: 0 }
}

```

## おまけ
### SassとPugをインストール

```
yarn add -D node-sass sass-loader pug pug-plain-loader
```

### Stroybook インストール

```
npx -p @storybook/cli sb init --type vue
```

```
yarn add -D path fork-ts-checker-webpack-plugin
```

TypeScript用にStorybookの `webpack.config.js` を編集

```.storybook/webpack.config.js
const path = require('path');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');

module.exports = {
  module: {
    rules: [
      {
        test: /\.ts$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'ts-loader',
            options: {
              appendTsSuffixTo: [/\.vue$/],
              transpileOnly: true
            },
          }
        ]
      },
      {
        test: /\.scss$/,
        use: [
          "style-loader", 
          "css-loader",
          {
            loader: "sass-loader",
            options: {
              importLoaders: 0,
              alias: {
                'assets': path.resolve(__dirname, '../app/assets')
              }
            }
          },
          {
            loader: "sass-resources-loader",
            options: {
              resources: [
                path.resolve(__dirname, '../app/assets/styles/variables/*.scss'),
                path.resolve(__dirname, '../app/assets/styles/common.scss')
              ]
            }
          }
        ]
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'file-loader'
      },
      {
        test: /\.pug$/,
        loader: 'pug-plain-loader'
      }
    ]
  },
  plugins: [
    new ForkTsCheckerWebpackPlugin()
  ],
  resolve: {
    extensions: ['.ts', '.js', '.vue', '.json'],
    alias: {
      'static': path.resolve(__dirname, '../app/static'),
      'assets': path.resolve('../app/assets'),
      '@': path.resolve(__dirname, '../app'),
      '~': path.resolve(__dirname, '../app')
    }
  }
};
```

---
コピペするとここまでが3分くらいでできるのではないでしょうか。