ぼくのかんがえたさいきょうのNuxt×TypeScript環境

この記事は [TypeScript Advent Calendar 2018](https://qiita.com/advent-calendar/2018/typescript) の13日目です。

# 2019/07/31追記

（ずっと前に）Nuxtが公式にTypeScriptをサポートしました。以下の煩わしい設定は一切不要で、以下の簡単な手順でプロジェクトにTypeScriptを導入できるようになりました。

# 2018/12/15追記

次のバージョンでTypeScriptのビルドオプションが追加されるようです。

> [feat: TypeScript support via `build` option](https://github.com/nuxt/nuxt.js/pull/4557)

# Nuxt × TypeScript

はじめまして。Advent Calendar初参戦の @simochee （しもちぃ）と申します。

Nuxt、最近流行っていますね。
そんなNuxtをTypeScriptで使おうと公式コミュニティのテンプレートを見に行ったら、もう半年以上更新されておらず若干不安になります。
（というか未だにNuxt 1.3.0だし）

ということで、NuxtでTypeScriptを使うための（個人的な）おすすめ環境をご紹介します。

なお、この記事ではNuxt 2.xでの使用を想定しています。

# Requirements

以下のツールが動作するような環境を目指します。

* TypeScript
* TSLint
* (ｵﾏｹ)Storybook

# Dependences

依存するパッケージの一覧です。

* TypeScript
  - ~~`typescript`~~
  - ~~`ts-loader`~~
  - ~~`fork-ts-checker-webpack-plugin`~~
* TSLint
  - `tslint`
  - `vue-tslint-loader`

```sh
# dependencies
$ yarn add typescript
```

```sh
# devDependencies
$ yarn add -D ts-loader fork-ts-checker-webpack-plugin tslint vue-tslint-loader
```

# `nuxt.config.js`

`nuxt.config.js`は`build.extend`と`build.plugins`、`extensions`を書き足します。

```js:nuxt.config.js（抜粋）
const ForkTsChecker = require('fork-ts-checker-webpack-plugin');

...

export default {
  build: {
    extend(config, { isDev }) {
      // ts-loaderの設定
      const tsLoader = {
        loader: 'ts-loader',
        options: {
          // .vueファイルもTypeScriptとして認識させる
          appendTsSuffixTo: [/\.vue$/],
          transpileOnly: true
        },
        exclude: [/vendor/, /\.nuxt/]
      };

      // Loaderを追加
      config.module.rules.push({ test: /\.tsx?$/, ...tsLoader });
      config.resolve.extensions.push('.ts');
      config.module.rules.map((rule) => {
        if (rule.loader === 'vue-loader') rule.options.loaders = { ts: tsLoader };
        return rule;
      });

      // TSLint
      config.module.rules.push({
        enforce: 'pre',
        test: /\.(tsx?|vue)$/,
        exclude: /(node_modules|\.nuxt)/,
        use: [
          {
            // vue-tslint-loaderでLintさせる
            loader: 'vue-tslint-loader',
            options: {
              configFile: 'tslint.yml',
              // 本番ビルド時にエラーが発生したら落ちるようにする
              emitErrors: !isDev
            }
          }
        ]
      });
    },
    plugins: [
      // 型チェックはForkTsCheckerに任せる
      new ForkTsChecker({
        vue: true
      }),
    ]
  },
  ...
  extensions: ['js', 'ts'],
  ...
}
```

これで、VueファイルでTypeScriptを使う準備が整いました。

# Vueファイル

TypeScriptを使ってページコンポーネントを書いてみましょう。

とは言っても、`<script>`に`lang="ts"`を付ければ普通にTypeScriptを書けます。

```vue:sample.vue（抜粋）
<script lang="ts">
import { SampleObj } from './interfaces.d.ts';

export defualt {
  data() {
    return {
      sample: {} as SampleObj
    }
  }
};
</script>
```

## `vue-property-decorator`

せっかくなので、Decoratorを使ってVueをClassベースに書くことができるライブラリ、`vue-property-decorator`を使ってみましょう。



# (ｵﾏｹ)Storybook

ついでに、StorybookでNuxt(Vue)とTypeScriptが使える環境もご紹介します。

## Dependences

* `@storybook/vue`
* `webpack-merge`
* `ts-loader`
* `vue-style-loader`
* `css-loader`
* `postcss-loader`

## `config.js`

`config.js`では、Nuxtのプラグインとグローバルなスタイル（`nuxt.config.js`の`css`プロパティ）を読み込んであげましょう。

```js:config.js（抜粋）
// グローバルのCSSを追加
import '@/assets/styles/main.sass';

// Nuxtのプラグインを追加
const plugins = require.context('../plugins', true, /\.ts$/);
plugins.keys().forEach(plugins);

// Storyを追加
const req = require.context('../stories', true, /\.stor(y|ies)\.tsx?$/);

function loadStories() {
  req.keys().forEach(filename => req(filename));
}

configure(loadStories, module);
```

## `webpack.config.js`

```js:webpack.config.js
const merge = require('webpack-merge');

module.exports = (storybookBaseConfig, configType, config) => {
  return merge(config, {
    module: {
      rules: [
        {
          test: /\.tsx?$/,
          loader: 'ts-loader',
          options: {
            appendTsSuffixTo: [/\.vue$/]
          }
        },
        {
          test: /\.sass$/,
          use: [
            'vue-style-loader',
            'css-loader',
            {
              loader: 'postcss-loader',
              options: {
                // postcssのプラグインを追加
              }
            }
          ]
        }
      ]
    },

    resolve: {
      alias: {
        '@': path.resolve(__dirname, '../src')
      },
      extensions: ['.js', '.ts']
    }
  });
};
```

以上で、`./stories/**/*.stor(y|ies).tsx?`にマッチするStoryが読み込まれます。