既存のVue.jsプラグインがVue 3で使えない場合の対応

いくつかのパターンをまとめましたが、簡単な修正だけで直るケースも多いです。
ただし、基本的にはプラグイン側の修正が必要なので、ここではその修正箇所をまとめています。

**目次:**

- [ケース1: this.$xxx() 系のプラグインで起きる問題](#ケース1-thisxxx-系のプラグインで起きる問題)
    - [Composition APIで使う](#composition-apiで使う)
- [ケース2: &lt;div v-xxx&gt;系のプラグインで起きる問題](#ケース2-div-v-xxx系のプラグインで起きる問題)
- [ケース3: コンポーネント系プラグインで起きる問題](#ケース3-コンポーネント系プラグインで起きる問題)
- [補足1: Vue.jsとVue 3両方に対応させる](#補足1-vuejsとvue-3両方に対応させる)
- [補足2: プラグインの修正ってどうやるの？](#補足2-プラグインの修正ってどうやるの)

# ケース1: this.$xxx() 系のプラグインで起きる問題

Vueインスタンスメソッドに機能が拡張されるタイプのプラグインで起きるのは、
**プラグインのインストール時、Vueのprototypeの拡張に失敗している**という事象です。

```
Uncaught TypeError: Object.defineProperty called on non-object
または
Uncaught TypeError: Cannot set property '$awesomeVuePlugin' of undefine
```

あたりのエラーが出ていないでしょうか？

### プラグインがどのように注入されるか

先にVueのプラグインのインストールについて少し説明させてください。

プラグインは`Vue.use()`に渡されますが、ここに渡されるオブジェクトには`install`というメソッドが実装されている必要があります。

`install`メソッドの第一引数には、Vueオブジェクトが渡されるので、それに対しdirectiveやmixin、prototypeなどを拡張することができます。

```js
export default {
  install (Vue) {
    // Vueのインスタンスメソッドとしてプラグインの機能を使えるようにする
    Vue.prototype.$awesomeVuePlugin = awesomeVuePlugin
  }
}
```

### Vue 3での変化

Vue 3では`Vue`オブジェクトは扱わなくなります。
初期化時は代わりに`createApp`というAPIで、アプリインスタンスを生成します。

```js:旧Vue.js
import Vue from 'vue'
Vue.use(プラグイン)
new Vue({ ... })
```

```js:Vue3
import { createApp } from 'vue'
const app = createApp(...)
app.use(プラグイン) // appインスタンスに対してuseします
```

そして`use()`したときに、プラグインの`install`メソッドに渡される引数も`Vue`ではなくそのアプリインスタンスになります。

このappはprototypeを持ちませんので、アサインしようとして`$xxx of undefined`となってしまいます。

```js
export default {
  install (app) {
    console.log(app.prototype) // -> undefined
    app.prototype.$xxx = ... // -> Error
  }
}
```

### 解決策: globalPropertiesを使う

Vue 3では、app.config.[globalProperties](https://v3.vuejs.org/api/application-config.html#globalproperties)を拡張するのがその代替となります。

```js:before
export default {
  install (Vue) {
    Vue.prototype.$awesomeVuePlugin = awesomeVuePlugin
  }
}
```

```js:after
export default {
  install (app) {
    app.config.globalProperties.$awesomeVuePlugin = awesomeVuePlugin
  }
}
```

## Composition APIで使う

上記の方法は、Vue3にバージョンだけ上げてOptions APIを使い続ける場合は問題ありませんが、

Composition APIを使う場合、setup内では`this`へアクセスできませんので、`this.$awesomeVuePlugin`といったことはそもそもできません。

なので、以下のようにインポートして使うような仕組みに直す必要があります。

```js
import { useAwesomeVuePlugin } from 'awesomeVuePlugin'
```

プラグイン側の修正量が多くなるかもしれません。

別の案としては、代わりに`provide` / `inject`を使う方法です。

### プラグイン内でprovideする

`import { provide } from 'vue'`ではなく、appインスタンスが待つ`provide`を使います。

```js
export default {
  install (app) {
    app.config.globalProperties.$awesomeVuePlugin = awesomeVuePlugin
    app.provide('awesomeVuePlugin', awesomeVuePlugin)
  }
}
```

```js
import { inject } from 'vue'
setup () {
  const awesomeVuePlugin = inject('awesomeVuePlugin')
}
```

但し、プラグインで勝手にprovideするのは親切にも、ありがた迷惑にもなりそうです。

自分でprovideさせる仕組みにする、provide名を指定できるようにする、プラグイン固有の名詞など明らかに衝突しない名前にする、といった工夫が必要だと思います。

参考までに、Vuexはprovideなし、Vue routerは`router`という名前で勝手にprovideしてくれていました。

# ケース2: &lt;div v-xxx&gt;系のプラグインで起きる問題

次に、ディレクティブが拡張されるタイプのプラグインです。

エラーなどもなくただ機能が動作しない、といった事象が起きていませんか？

Vue 3では、**カスタムディレクティブで用いられるメソッドの名前が変わった**ことが原因と思われます。

### 解決策: 新しいメソッド名に修正する

|Vue.js|Vue 3|
|---|---|
|bind|beforeMount|
|inserted|mounted|
|-|beforeUpdate|
|update|-|
|componentUpdated|updated|
|-|beforeUnmount|
|unbind|unmounted|

Ref: https://v3.vuejs.org/guide/migration/custom-directives.html#_3-x-syntax
無くなった`update`は`updated`を使ってください。

```js:before
app.directive('xxx', {
  inserted (el, binding, vnode) {
    el.style.background = binding.value
  }
  unbind () {}
})
```

```js:after
app.directive('xxx', {
  mounted (el, binding, vnode) {
    el.style.background = binding.value
  }
  unmounted () {}
})
```

# ケース3: コンポーネント系プラグインで起きる問題

```js
import Slider from 'vue-awesome-slider'
export default {
  components: { Slider }
}
```

[色々な変更](https://v3.vuejs.org/guide/migration/introduction.html)が影響している可能性があります。

僕が遭遇したのはライフサイクルフックの名称変更による問題です。
`destroyed`が`unmounted`に変わっており、コンポーネント破棄時の処理が呼ばれない問題が起きていました。

```js:before
Slider = {
  created () {},
  destroyed () {}
}
```

```js:after
Slider = {
  created () {},
  unmounted () {}
}
```

# 補足1: Vue.jsとVue 3両方に対応させる

アプリインスタンスも`Vue`オブジェクトも`version`というプロパティから使われているVueのバージョンを取得可能です。

これを利用して実装を分岐しましょう。

```js
export default {
  install (app) {
    console.log(app.version) // -> '3.0.0-rc.12'

    const isVue3 = app.version.startsWith('3')

    // インスタンスメソッドの拡張
    const prototype = isVue3 ? app.config.globalProperties : app.prototype
    prototype.$awesomeVuePlugin = awesomeVuePlugin

    // ディレクティブの拡張
    app.directive('xxx', {
      [isVue3 ? 'mounted' : 'inserted'] (el, binding, vnode) {
        el.style.background = binding.value
      }
      [isVue3 ? 'unmounted' : 'unbind'] () {}
    })
  }
}
```

# 補足2: プラグインの修正ってどうやるの？

本題とは逸れますが、そもそもインストールしてきたプラグインをどのように修正すればよいか。

1. プラグインのGitHubリポジトリを、自分のアカウントにForkする
2. 該当箇所を見つけて修正する
3. 本家リポジトリに（リポジトリのルールに沿って）プルリクを出す
4. 本家がアップデートされるまでは`yarn add GitHubユーザー名/awesome-vue-plugin` で自分がForkしたものをインストールする