# Volar で Vuetify2.x  の補完を効かせる

先日 [Vue2.7](https://github.com/vuejs/vue/blob/main/CHANGELOG.md#270-2022-07-01) がリリースされたので早速アップデートしてきました。

Vue2.7 では 3.x の機能である [Composition](https://vuejs.org/guide/extras/composition-api-faq.html) API や[script setup](https://vuejs.org/api/sfc-script-setup.html) がバックポートされており、3.x にすぐにアップデートできないプロジェクトでも最新の構文を使用できます。

さらに 2.7 では依存関係の追加なしで [Volar](https://github.com/johnsoncodehk/volar) をサポートしています。

https://github.com/vuejs/vue/blob/main/CHANGELOG.md#volar-compatibility

Volar は template 内での型サポートがあるなど、Vuter よりもさらに高い開発者体験を提供してくれます。早速 Volar を導入してみたのですが、1 つ問題が発生しました。

## Vuetify の補完が効かない

Vuter から Volar に移行した際に Vuetify の補完が効かなくなるという問題が発生しました。以下の Issue によると Vuetify 2.x では Volar のサポートを提供する予定はないようです。

> Yes this is only v3, for v2 you need to use vetur instead.

https://github.com/vuetifyjs/vuetify/issues/14798#issuecomment-1062469925

というわけで、自力で Vuetify の型定義ファイルを作成する必要があります。

## Volar でグローバルコンポーネントを定義する

Vuetify の補完が効かない理由としては、Vuetify のコンポーネントがグローバルコンポーネントとして登録されているためです。これを解決するためには型定義ファイルを作成して Volar にグローバルコンポーネントが存在することを教えてあげる必要があります。

Volar でグローバルコンポーネントを定義する方法は以下のように記載があります。

> Define Global Components
> PR: https://github.com/vuejs/vue-next/pull/3399
> 
> Local components, Built-in components, native HTML elements Type-Checking is available with no configuration.
> 
> For Global components, you need to define GlobalComponents interface, for example:
> 
> ```ts
> // components.d.ts
> declare module '@vue/runtime-core' {
>   export interface GlobalComponents {
>     RouterLink: typeof import('vue-router')['RouterLink']
>     RouterView: typeof import('vue-router')['RouterView']
>   }
> }
> 
> export {}
> ```

https://marketplace.visualstudio.com/items?itemName=Vue.volar

まずは `d.ts` ファイルを作成します。ここで `declare module '@vue/runtime-core' {}` を宣言し、`GlobalComponents` を拡張してあげればよいわけです。

もし Vue 2.6.14 <= を使っている場合には `@vue/runtime-core` の代わりに `@vue/runtime-dom` と記載します。

上記の構文に従って Vuetify に存在するコンポーネントを記述していけばよいわけなのですが、1 つづつ手作業で行うのは骨が折れます。

有志の方が Vuetify の型定義を作成するスクリプトを作成しているので、ありがたく使わせていただきましょう。

```ts
// ./scripts/vuetify-type.mjs
import webTypes from 'vuetify/dist/json/web-types.json' assert { type: 'json' }

const blackList = ['VFlex', 'VLayout'] // Components not to define in global

function convertType(typeStr) {
  switch (typeStr) {
    case 'array':
      return 'any[]'
    case 'function':
      return 'Function'
    case 'date':
      return 'Date'
    case 'regexp':
      return 'RegExp'
    default:
      return typeStr
  }
}

function getType(attrType) {
  if (typeof attrType === 'string') {
    return convertType(attrType)
  } else {
    return attrType.map((str) => convertType(str)).join('|')
  }
}

const types = webTypes.contributions.html.tags
  .map((vm) =>
    !blackList.includes(vm.name)
      ? vm.name +
        ': DefineComponent<{' +
        vm.attributes
          .map(
            (attr) =>
              (attr.description ? `/** ${attr.description} */\n` : '') +
              `${attr.name.replace(/-./g, (x) =>
                x[1].toUpperCase()
              )}?: ${getType(attr.value.type)}`
          )
          .join('\n') +
        '}>'
      : ''
  )
  .join('\n')

console.log(`
import type { DefineComponent } from '@vue/runtime-dom'
import type { DataTableHeader, DataOptions } from 'vuetify'

declare module '@vue/runtime-dom' {
  export interface GlobalComponents {
    ${types}
  }
}

export {}`)
```

https://github.com/vuetifyjs/vuetify/issues/14798#issuecomment-1139788615

注意点として、Vue 2.7.0 <= の場合には `'@vue/runtime-dom'` を `'@vue/runtime-dom'` に置換する必要があります。

後は上記のスクリプトを実行してプロジェクトに型定義ファイルを配置すれば、Vuetify のコンポーネントの補完が効くはずです。

```
node ./scripts/vuetify-type.mjs > ./src/vuetify2.d.ts
```

### トラブルシューティング

#### スクリプト実行後も変化がない

Ctrl+Shift+P で command palette 表示し、`Restart Vue Server` を実行してください。

![スクリーンショット 2022-07-10 14.44.29](//images.ctfassets.net/in6v9lxmm5c8/01N4gTMe9BSQOcUvneI15F/4b5a274c6dfff14333f8a6f0ef363de2/____________________________2022-07-10_14.44.29.png)

#### Vuetify のコンポーネントの型が `any` になる

スクリプト実行後、コンポーネント名は補完されるものの型が `any` となっていて Props や Emit の型が効かなくなることがあります。

そのような場合はおそらく Vue CLI によって自動生成された `src/shims-tsx.d.ts` ファイルを削除すれば大丈夫なはずです。

Volar の説明でも Vue CLI によって自動生成されたファイルを削除するように記載があります。

> remove .d.ts files if they exist.
> For projects generated by the Vue CLI, .d.ts files are included. Remove these files.
> 
> ```
> rm src/shims-tsx.d.ts src/shims-vue.d.ts
> ```

https://marketplace.visualstudio.com/items?itemName=Vue.volar

#### スクリプトの実行がエラーになる

Node.js v16.14.0 <= にアップデートしてください。

スクリプトでは [import assersion](https://simonplend.com/import-json-in-es-modules/) 構文を使用しているためです。 
  