【Vue.js】Vuetify と TypeScript を使用した環境を構築してサンプルプロジェクトを立ち上げるまでの手順

# はじめに

Vue.js 用のマテリアルデザインフレームワークである`Vuetify`と JavaScript の静的型付けのスーパーセットである TypeScript を使用したプロジェクトの環境を構築しサンプルを立ちあげるまでの手順です。

## Vue-CLI のインストール

Vue-CLI でひな型を作成するのが簡単なのでインストールします。

```shell
# Vue-CLI をグローバルインストールします。
$ yarn global add @vue/cli

# 動作確認を兼ねてバージョン確認
$ vue -V
3.1.3
```

## プロジェクトの作成

```shell
$ vue create vue-typescript-vuetify-starter
```
`? Please pick a preset:`で`Manually select features`を選択します。
以下のように `Check the features needed for your project:`で **TypeScript** を選択します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/195024/b130d6da-8708-71bf-1914-a9c06bff3b1c.png)

正常にプロジェクトが作成されると以下のように表示されます。

```shell
# 略

🎉  Successfully created project vue-typescript-vuetify-starter.
👉  Get started with the following commands:

 $ cd vue-typescript-vuetify-starter
 $ yarn serve
```

##  Vuetify パッケージの追加

```sh
# プロジェクトのディレクトリに移動します。
cd vue-typescript-vuetify-starter
# Vuetify を追加します
vue add vuetify
```

`vuetify`プラグインを追加すると色々とファイルが追加・上書きされています。
差分：https://github.com/sano-suguru/vue-typescript-vuetify-starter/commit/3318e1f5480ea6119d9d54924ba7f7214e74f1a7

```sh
$ git status

On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   package.json
        modified:   public/index.html
        modified:   src/App.vue
        modified:   src/components/HelloWorld.vue
        modified:   src/main.ts
        modified:   yarn.lock

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        src/assets/logo.svg
        src/plugins/
```

## ファイルの修正

アプリをビルドし立ち上げます。

```shell
$ yarn serve
```

ビルドは終了しますが、以下のように`ERROR`と`WARNING`が発生します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/195024/05a3f25c-1769-37cf-d179-49a6a88606ff.png)

順番に修正します。修正箇所については GitHub へのリンクを張りました。


### TypeScript コンパイラオプションの追加

- `tsconfig.json`の`compilerOptions.types`に`"vuetify"`を追加します。

**[`tsconfig.json`の修正箇所へのリンク - GitHub](https://github.com/sano-suguru/vue-typescript-vuetify-starter/commit/111bb502e856946abf5cecdaa678ca3ae82a497f#diff-e5e546dd2eb0351f813d63d1b39dbc48)**

※以下のように、`vuetify`の型定義を用意しろとエラーメッセージが表示されていますが、上記の方法で OK です。

```
Could not find a declaration file for module 'vuetify/lib'.
Try `npm install @types/vuetify` if it exists or add a new declaration (.d.ts) file containing `declare module 'vuetify/lib';`
```

```json:tsconfig.json
 {
   "compilerOptions": {
     "target": "esnext",
     "module": "esnext",
     "strict": true,
     "jsx": "preserve",
     "importHelpers": true,
     "moduleResolution": "node",
     "experimentalDecorators": true,
     "esModuleInterop": true,
     "allowSyntheticDefaultImports": true,
    "sourceMap": true,
    "baseUrl": ".",
    "types": [
      "webpack-env",
      "vuetify"
    ],
    "paths": {
      "@/*": [
         "src/*"
       ]
     },
     "lib": [
       "esnext",
       "dom",
       "dom.iterable",
       "scripthost"
     ]
   },
   "include": [
     "src/**/*.ts",
     "src/**/*.tsx",
     "src/**/*.vue",
     "tests/**/*.ts",
     "tests/**/*.tsx"
   ],
   "exclude": [
     "node_modules"
   ]
 }
```

### コンポーネントの修正

- `<script>`タグの`lang`属性を`ts`に変更
- 単一ファイルコンポーネントのインポートは拡張子`.vue`まで記述
- コンポーネントを`class`コンポーネント化
- カンマ`,`、セミコロン`;`の追加

言語属性を TypeScript に変更すると単一ファイルコンポーネントは拡張子まで記述が必要になります。
Vue-CLI で Vuetify を追加した際にコンポーネントがクラスから通常のものに上書きされているのでクラス化します。
カンマ`,`、セミコロン`;`については`tslint.json`を修正しても良いですが、今回は`Vue-CLI`で作成したデフォルトの規約に合わせます。

**[`App.vue`の修正箇所へのリンク - GitHub](https://github.com/sano-suguru/vue-typescript-vuetify-starter/commit/111bb502e856946abf5cecdaa678ca3ae82a497f#diff-ea994a51b0d9884f4fc046eca11a5c99)**

```vue:App.vue
<template>
  <v-app>
    <v-toolbar app>
      <v-toolbar-title class="headline text-uppercase">
        <span>Vuetify</span>
        <span class="font-weight-light">MATERIAL DESIGN</span>
      </v-toolbar-title>
      <v-spacer></v-spacer>
      <v-btn flat href="https://github.com/vuetifyjs/vuetify/releases/latest" target="_blank">
        <span class="mr-2">Latest Release</span>
      </v-btn>
    </v-toolbar>

    <v-content>
      <HelloWorld/>
    </v-content>
  </v-app>
</template>

<script lang="ts">
import { Vue, Component } from 'vue-property-decorator';
import HelloWorld from './components/HelloWorld.vue';
@Component({
  components: {
    HelloWorld,
  },
})
export default class App extends Vue {}
</script>
```

**[`HelloWorld.vue`の修正箇所へのリンク - GitHub](https://github.com/sano-suguru/vue-typescript-vuetify-starter/commit/111bb502e856946abf5cecdaa678ca3ae82a497f#diff-4736853c62ba81ab6486ff1f0c13eada)**

```vue:HelloWorld.vue
<template>
  <v-container>
    <v-layout text-xs-center wrap>
      <v-flex xs12>
        <v-img :src="require('../assets/logo.svg')" class="my-3" contain height="200"></v-img>
      </v-flex>

      <v-flex mb-4>
        <h1 class="display-2 font-weight-bold mb-3">Welcome to Vuetify</h1>
        <p class="subheading font-weight-regular">
          For help and collaboration with other Vuetify developers,
          <br>please join our online
          <a
            href="https://community.vuetifyjs.com"
            target="_blank"
          >Discord Community</a>
        </p>
      </v-flex>

      <v-flex mb-5 xs12>
        <h2 class="headline font-weight-bold mb-3">What's next?</h2>

        <v-layout justify-center>
          <a
            v-for="(next, i) in whatsNext"
            :key="i"
            :href="next.href"
            class="subheading mx-3"
            target="_blank"
          >{{ next.text }}</a>
        </v-layout>
      </v-flex>

      <v-flex xs12 mb-5>
        <h2 class="headline font-weight-bold mb-3">Important Links</h2>

        <v-layout justify-center>
          <a
            v-for="(link, i) in importantLinks"
            :key="i"
            :href="link.href"
            class="subheading mx-3"
            target="_blank"
          >{{ link.text }}</a>
        </v-layout>
      </v-flex>

      <v-flex xs12 mb-5>
        <h2 class="headline font-weight-bold mb-3">Ecosystem</h2>

        <v-layout justify-center>
          <a
            v-for="(eco, i) in ecosystem"
            :key="i"
            :href="eco.href"
            class="subheading mx-3"
            target="_blank"
          >{{ eco.text }}</a>
        </v-layout>
      </v-flex>
    </v-layout>
  </v-container>
</template>

<script lang="ts">
import { Vue, Component } from 'vue-property-decorator';
@Component
export default class HelloWorld extends Vue {
  private ecosystem = [
    {
      text: 'vuetify-loader',
      href: 'https://github.com/vuetifyjs/vuetify-loader',
    },
    {
      text: 'github',
      href: 'https://github.com/vuetifyjs/vuetify',
    },
    {
      text: 'awesome-vuetify',
      href: 'https://github.com/vuetifyjs/awesome-vuetify',
    },
  ];
  private importantLinks = [
    {
      text: 'Documentation',
      href: 'https://vuetifyjs.com',
    },
    {
      text: 'Chat',
      href: 'https://community.vuetifyjs.com',
    },
    {
      text: 'Made with Vuetify',
      href: 'https://madewithvuetifyjs.com',
    },
    {
      text: 'Twitter',
      href: 'https://twitter.com/vuetifyjs',
    },
    {
      text: 'Articles',
      href: 'https://medium.com/vuetify',
    },
  ];
  private whatsNext = [
    {
      text: 'Explore components',
      href: 'https://vuetifyjs.com/components/api-explorer',
    },
    {
      text: 'Select a layout',
      href: 'https://vuetifyjs.com/layout/pre-defined',
    },
    {
      text: 'Frequently Asked Questions',
      href: 'https://vuetifyjs.com/getting-started/frequently-asked-questions',
    },
  ];
}
</script>

<style>

</style>
```

### その他

- `main.ts` `vuetify.ts`にセミコロン`;`を追加

`vue add vuetify`で追加されたコードにはセミコロンがついていないので修正します。

**[`main.ts`の修正箇所へのリンク - GitHub](https://github.com/sano-suguru/vue-typescript-vuetify-starter/commit/111bb502e856946abf5cecdaa678ca3ae82a497f#diff-8cfead41d88ad47d44509a8ab0a109ad)**

```ts:main.ts
import Vue from 'vue';
import './plugins/vuetify';
import App from './App.vue';

Vue.config.productionTip = false;

new Vue({
  render: (h) => h(App),
}).$mount('#app');
```

**[`vuetify.ts`の修正箇所へのリンク - GitHub](https://github.com/sano-suguru/vue-typescript-vuetify-starter/commit/111bb502e856946abf5cecdaa678ca3ae82a497f#diff-6efd9e86665c698fe60227fb59356369)**

```ts:vuetify.ts
import Vue from 'vue';
import Vuetify from 'vuetify/lib';
import 'vuetify/src/stylus/app.styl';

Vue.use(Vuetify, {
  iconfont: 'md',
});
```

## 終りに

上記の修正でエラーが全て消えて正常にコンパイルができます。
全て修正済みのソースのリポジトリ：**[vue-typescript-vuetify-starter](https://github.com/sano-suguru/vue-typescript-vuetify-starter)**


![image.png](https://qiita-image-store.s3.amazonaws.com/0/195024/5905fd1c-83e8-7771-d953-ae7793b86f74.png)