✨ mo.js に恋して（あっ 💦 Vue の話だよ！） ✨

とある地方――、 Vue.js で開発をしているときのお話。
筆者はクリックイベントにアニメーションをつけたく、アニメーションフレームワークをネットで漁っていた。

「お！　これいいじゃん」

![mojs.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317946/6be2c55b-a02c-f9ea-e07c-9f5738563d43.gif)


「 めっちゃかっこいい……。mo.js って言うのか」

おもむろにノート PC のキーボードを叩き始める筆者。
ディスプレイに映し出された検索サイトの入力欄には `mo.js Vue` とスムーズに打ち込まれ、タンッという打鍵音とともにページが再描画された。

「 Vue 用のパッケージは……、ほとんどないか」



◆　◇　◆　◇　◆



ということで、 **mo.js を Vue で使いやすいようにプラグイン化して npm リリースした** ので、そこで得た知見をまとめました。



## 🏹 TL;DR

ターゲットとなる読者がわかりづらいのでまとめると、

- インスタンスメソッドをもつプラグインの作成
- Vue コンポーネントをもつプラグインの作成
- Vue カスタムディレクティブをもつプラグインの作成
- それら全部乗せのプラグインの作成
- プラグインの npm パッケージ化

といった感じです。



npm パッケージ化は `Vue CLI 3` で行っています。



## 🔫 できあがったもの

`vue-mo.js` という npm パッケージをリリースしました。

[✨ デモページ ✨](https://azukisiromochi.github.io/vue-mo.js/) があるので良かったら見てみてください。

![vue-mojs-demo.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317946/40c83ba6-ad8b-71ba-d4f4-ef00f3b7dc19.gif)


ソースコードも GitHub で公開していますので、興味のある方はどうぞ。

:octocat: [azukisiromochi/vue-mo.js | GItHub](https://github.com/azukisiromochi/vue-mo.js)

:link: [@azukisiromochi/vue-mo.js | npm](https://www.npmjs.com/package/@azukisiromochi/vue-mo.js)



## 🧹 プラグイン開発と npm パッケージ化まで

それでは本題、開発についてです。

類似記事は多いのですが、プラグインとしてインスタンスメソッド、コンポーネント、カスタムディレクティブをまとめてドン！　みたいなのは少なかったので、なるべく体系的にまとめてみました。



### ⭐ インスタンスメソッドをもつプラグイン

#### どんなの？

Vue にグローバルレベルで機能を追加したものをプラグインといいます。

```javascript
this.$vuemo.Star({
  parent: this.$refs.starParent
})
.play()
```

のように、ソースコード上で `this` （ `Vue` ）から直接、開発したプラグインを参照することができるようになります。



#### プラグインの作成

プラグインと言ってもいろいろなものがありますが、今回は主に **インスタンスメソッド・プロパティを追加** することで、プラグインとして用意したメソッドやプロパティが Vue インスタンスで利用できるようになるものを作っていきます。

```javascript

const Burst = function(binding) {
    // mo.js の Burst を利用するための関数
}

const Vuemo = {
  install: function(Vue, options) {
    Vue.prototype.$vuemo = {
      Burst,
      // any...
    }
  }
}

export default Vuemo
```

こんな感じで `install` メソッドをもつオブジェクトを定義して、 `export` すればプラグインとして利用ができます。

この例では、 `install` メソッドで `Vue.prototype` に `$vuemo` というオブジェクトを追加していて、 `$vuemo` は `Burst` という mo.js の Burst を利用するための関数を持ったプラグインです。



#### 使い方

まずはインポートして `Vue.use` しましょう。

```javascript
// プロジェクト内のプラグインなら `@/plugins/vuemo.js` みたいなパスを.
import Vuemo from '@azukisiromochi/vue-mo.js'
Vue.use(Vuemo)
```

あとは簡単、 `this.$vuemo` でアクセスできます。

```html
<template>
  <button type=button ref="vuemoElement" v-on:click="replay">Burst!</button>
</template>

<script>
export default {
  data() {
    return {
      burst: null
    }
  },
  mounted() {
    this.burst = this.$vuemo.Burst({
      parent: this.$refs.vuemoElement,
      radius: { 25: 75 },
      count: 10,
      duration: 2000,
      children: {
        shape: ["circle", "polygon"],
        fill: ["#11CDC5", "#FC2D79", "#F9DD5E"],
        angle: { 0: 180 },
        degreeShift: "rand(-360, 360)",
        delay: "stagger(0, 25)"
      }
    })
  },
  methods: {
    replay: function() {
      this.burst.replay()
    }
  }
}  
</script>
```

`vue-mo.js` プラグインを利用したデモページのソースコードの一部ですが、 `mounted` 内でプラグインを活用しています。

コンポーネントの `data` に Burst （爆発のようなエフェクトアニメーション）関数をもたせて、ボタンクリックでアニメーションするようになっています。
（ちなみに、上に貼ったデモページの gif 画像も Burst を使っています）



#### 参考

:link: [プラグイン | Vue.js 公式](<https://jp.vuejs.org/v2/guide/plugins.html>)



### ⭐ Vue コンポーネントをもつプラグイン

#### どんなの？

Vue を使ったことがある人なら大抵はコンポーネントを作成して利用していると思います。

```html
<font-awesome-icon icon="coffee"></font-awesome-icon>
```

これは Font Awesome の例ですが、コンポーネントを `import` することでカスタム要素として利用できるようになります。



#### プラグインの作成

`.vue` ファイルで定義されたコンポーネントをパッケージ化して利用する、実はこれも **プラグインにコンポーネントを追加** することで実現できます。

```javascript
import _MojsBurst from "@/components/MojsBurst.vue"

export default const MojsBurst = {
  install(Vue, options){
    Vue.component("MojsBurst", _MojsBurst)
  }
} 
```

『⭐ インスタンスメソッドをもつプラグイン』のときと同じですね。

`install` メソッドを持つオブジェクトを `export` しています。

ただ、 `install` メソッド内では `Vue.component` により作成したコンポーネントを追加しています。

これでコンポーネントをもったプラグインの出来上がりです。



#### 使い方

インポート & `Vue.use` は同じなので省略。

```html
<template>
  <mojs-burst
    :options="burstOptions"
    :is-replay-when-clicked="true"
    class="any-style" />
</template>

<script>
// プロジェクト内のプラグインなら `@/plugins/vuemo.js` みたいなパスを.
import MojsBurst from "@azukisiromochi/vue-mo.js" 
export default {
  data() {
    return {
      burstOptions: {
        radius: { 25: 75 },
        count: 10,
        duration: 2000,
        children: {
          shape: ["circle", "polygon"],
          fill: ["#11CDC5", "#FC2D79", "#F9DD5E"],
          angle: { 0: 180 },
          degreeShift: "rand(-360, 360)",
          delay: "stagger(0, 25)"
        }
      }
    }
  },
  components: {
    MojsBurst
  }
}
</script>
```

`MojsBurst` というコンポーネントは、カスタム要素 `<mojs-burst>` に対して Burst が発火するクリックイベントが設定されています。

`options` 属性に Burst の設定（エフェクトの種類など）を設定して利用します。



#### 参考

:link: [コンポーネントの登録 | Vue.js 公式](<https://jp.vuejs.org/v2/guide/components-registration.html>)



### ⭐ Vue カスタムディレクティブをもつプラグイン

#### どんなの？

Vue の標準セットである `v-if` や `v-model` のようなディレクティブを自作したものがカスタムディレクティブです。

```html
<button
  type=button
  v-mojs-star-burst="{ burstShape: 'star' }">
  ⭐Star Burst⭐
</button>
```

html 要素に `v-` 始まりの属性を設定することで独自の機能を付与することができます。



#### プラグインの作成

カスタムディレクティブの作成は、やったことがない人もいると思いますので、まずはそこの説明から。

```javascript
const MojsBurstDirective = {
  bind: function(el, binding) {
    const options = binding.value || {}
    options.parent = el

    const burst = new mojs.Burst(options)

    el.addEventListener("click", function(e) {
      const left = e.pageX - el.offsetLeft
      const top = e.pageY - el.offsetTop
      burst.tune({ left, top }).replay()
    })
  }
}
```

この例では `MojsBurstDirective` というカスタムディレクティブを作成しています。
ディレクティブが初めて対象の要素にひも付いたときに１度だけ呼ばれるフック関数 `bind` を定義していて、クリックした場所に Burst を用いたエフェクトアニメーションが表示されるように設定しています。

ディレクティブのフック関数は、 `bind` 以外にもありますので、軽く紹介しておきます。

| フック関数 | 概要 |
|:-:|:---|
| bind |  ディレクティブが初めて対象の要素にひも付いた時に 1 度だけ呼ばれます。ここで 1 回だけ実行するセットアップ処理を行えます。 |
| inserted | ひも付いている要素が親 Node に挿入された時に呼ばれます。(これは、親 Node が存在している時にだけ保証します) |
| update | ひも付いた要素を抱合しているコンポーネントの VNode が更新される度に呼ばれます。子コンポーネントが更新される前になるので、バインディングされている値と以前の値との比較によって不要な更新を回避することができます。 |


次に、このディレクティブをプラグイン化します。

```javascript
const MojsBurstDirective = {
  bind: function(el, binding) {
    // 上を参照.
  }
}

export default const MojsBurst = {
  install(Vue, options){
    Vue.directive("mojs-burst", MojsBurstDirective)
  }
} 
```

3 度めなので流石に慣れてきました。

今回は、 `install` メソッド内で `Vue.directive` により作成したディレクティブを追加しています。

これでディレクティブをもったプラグインの出来上がりです。



#### 使い方

例のごとく、インポート & `Vue.use` は同じなので省略。

```html
<template>
  <button v-mojs-burst:[arg]="burstOptions">Burst!</button>
</template>

<script>
export default {
  data() {
    return {
      burstOptions: {
        radius: { 25: 75 },
        count: 10,
        duration: 2000,
        children: {
          shape: ["circle", "polygon"],
          fill: ["#11CDC5", "#FC2D79", "#F9DD5E"],
          angle: { 0: 180 },
          degreeShift: "rand(-360, 360)",
          delay: "stagger(0, 25)"
        }
      },
      arg: 'is-replay-when-clicked'
    }
  }
}
</script>
```

ボタン要素を `v-mojs-burst` ディレクティブとして利用しています。

コンポーネントのときと同じように、ディレクティブに Burst の設定（エフェクトの種類など）を渡して設定しています。

また、 `arg` はディレクティブ引数で、このディレクティブではクリックするたびにイベント発火させるかを判断させるために利用しています。



#### 参考

:link: [カスタムディレクティブ | Vue.js 公式](<https://jp.vuejs.org/v2/guide/custom-directive.html>)



### ⭐ それら全部乗せのプラグインの作成

これまで紹介した 3 種類のプラグインをひとつにまとめます。

```javascript
import _MojsBurst from "@/components/MojsBurst.vue"

const MojsBurstDirective = {
  bind: function(el, binding) {
    // 上を参照.
  }
}

const Vuemo = {
  install: function(Vue, options) {
    Vue.prototype.$vuemo = {
      Burst,
      // any...
    }
    Vue.directive("mojs-burst", MojsBurstDirective)
    Vue.component("MojsBurst", _MojsBurst)
  }
};
export default Vuemo
export const MojsBurst = _MojsBurst
```

なんだ、混ぜただけじゃないか――と思うかもしれませんが、よく見てください。

最後に `export const MojsBurst = _MojsBurst` という 1 行が追加されています。

`export default` は default というだけあって、ひとつしか書くことができません。

コンポーネントのみをプラグイン化する場合はよかったですが、 **プラグインが複数の機能をもつような場合はコンポーネントは別途 `export` する必要があります** 。



また、利用する際も同様に、

```javascript
import { MojsBurst } from "@azukisiromochi/vue-mo.js" 
```

と書く必要があります。

Font Awesome でよく使われているやつですね。



### ⭐ プラグインの npm パッケージ化

作成したプラグインを npm パッケージとして公開していく手順をまとめます。



#### package.json を編集

`package.json` に必要情報を記載します。

```javascript
{
  // 公開するパッケージ名.
  "name": "your-package",
  "version": "1.0.0",
  // 以下３つの `your-package` のところはパッケージ名に応じて変える.
  "main": "dist/your-package.common.js",
  "unpkg": "dist/your-package.umd.min.js",
  "jsdelivr": "dist/your-package.umd.min.js",
  // ライセンス.
  "license": "MIT", 
  // 作成者名.
  "author": "your-name",
  "files": [
    "dist"
  ],
  // デフォルト（true）のままだと公開できないため `false` に.
  "private": false,
  // GitHub などのリポジトリ情報を記載しておくと npm のパッケージ画面に表示される.
  "repository": {
    "type": "git",
    "url": "https://github.com/xxxxx/xxxxx"
  },
  // キーワードを記載しておくと npm のパッケージ画面に表示される.
  "keywords": [
    "vue",
    "mo.js"
  ],
  "scripts": {
    // ライブラリビルド用のスクリプト.
    // `--name` のあとにパッケージ名、プラグインがコーディングされているファイルパスと続くので記載する.
    "build-bundle": "vue-cli-service build --target lib --name your-package ./src/main.js"
  },
  // dist のみ公開する場合は、 "dependencies": {} でOK
  "dependencies": {
    // パッケージで外部ライブラリなど使用している場合は記載する（ npm install おまかせでいい）
    // "@mojs/core": "^0.288.2",  
    "core-js": "^3.3.2",
    "vue": "^2.6.10"
  },
```





#### プラグインをビルド

npm パッケージとして公開するためには、ビルド済みのプラグインが必要です。

先程の `package.json` にスクリプトを設定済みのため、

```shell
$ npm run build-bundle
```

コマンドでビルドを行います。

ビルドが完了したら、

![dist.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317946/acf9453c-0f2c-7b2e-327c-5e0fec9a0b83.png)


のように `dist` ディレクトリが作成され、ビルド後の JavaScript 資産などが生成されているはずです。



#### npm に公開

これはたくさんの方が記事に書かれているので、省略しますが、こちらの記事がわかりやすいと思います。



:link: [初めてのnpm パッケージ公開 | Qiita]([https://qiita.com/TsutomuNakamura/items/f943e0490d509f128ae2#npm-%E3%81%AB%E5%85%AC%E9%96%8B](https://qiita.com/TsutomuNakamura/items/f943e0490d509f128ae2#npm-に公開))



ちなみに、 `npm publish` を実行したときにエラーが返ることがあります。
エラーメッセージを取りそこねましたが、『すでに似たパッケージ名あるよ！』みたいな感じのものです。

npm では、 `-` や `_` 、 `.` などを区別せずにチェックしているようで、それを踏まえてパッケージ名を決めましょう。
どうしてもエラーになるパッケージ名を使いたい場合は、パッケージ名を `@your-name/your-package` のように npm アカウント名を付与する形で命名して、

```shell
$ npm publish --access=public
```

とコマンドを実行すれば公開することができます。



:link: [[solving npm’s hard problem: naming packages | the npm blog](https://blog.npmjs.org/post/116936804365/solving-npms-hard-problem-naming-packages)]



## 🙇 おわりに

[Vue #2 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/vue2) の13日目の記事でした。

もともと Qiita 記事を書くつもりでしたが、ちょうどアドベントカレンダーに空きがあったので差し込みました。
（ 2 日前に思い立ったのでギリギリ 💦 ）



`Composition API` で話題がもちきりななか基本的な内容になりましたが、どなたかの役に立つと嬉しいです :yum:



あと、 [mo.js](https://mojs.github.io/) いい感じだからみんな使ってみてよ！（ `vue-mo.js` もね！）



◆　◇　◆　◇　◆



> とある地方――、 Vue.js で開発をしているときのお話。
> 筆者はクリックイベントにアニメーションをつけたく、アニメーションフレームワークをネットで漁っていた。

プラグイン作るのに夢中で、本来の目的がおざなりになっていることを筆者はまだ知らない――。



◆　◇　◆　◇　◆

12日目の記事 :arrow_right: @yaju さんの [Handsontable for Vueを使ってみる](https://qiita.com/yaju/items/7bf9cb45bf180b6a600c)

14日目の記事 :arrow_right: @kokky さんの [VueとVue Routerで、リダイレクトのない理想の404を目指す](https://qiita.com/kokky/items/3d0a32767ee3bfc298f2)