Help us understand the problem. What is going on with this article?

✨ mo.js に恋して(あっ 💦 Vue の話だよ!) ✨

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

「お! これいいじゃん」

mojs.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 パッケージをリリースしました。

✨ デモページ ✨ があるので良かったら見てみてください。

vue-mojs-demo.gif

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

:octocat: azukisiromochi/vue-mo.js | GItHub

:link: @azukisiromochi/vue-mo.js | npm

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

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

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

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

どんなの?

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

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

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

プラグインの作成

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

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 というオブジェクトを追加していて、 $vuemoBurst という mo.js の Burst を利用するための関数を持ったプラグインです。

使い方

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

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

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

<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 公式

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

どんなの?

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

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

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

プラグインの作成

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

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

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

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

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

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

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

使い方

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

<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 公式

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

どんなの?

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

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

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

プラグインの作成

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

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

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

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

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

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

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

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

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

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

使い方

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

<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 公式

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

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

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 する必要があります

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

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

と書く必要があります。

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

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

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

package.json を編集

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

{
  // 公開するパッケージ名.
  "name": "your-package",
  "version": "1.0.0",
  // 以下3つの `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 にスクリプトを設定済みのため、

$ npm run build-bundle

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

ビルドが完了したら、

dist.png

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

npm に公開

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

:link: 初めてのnpm パッケージ公開 | Qiita

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

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

$ npm publish --access=public

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

:link: [solving npm’s hard problem: naming packages | the npm blog]

🙇 おわりに

Vue #2 Advent Calendar 2019 の13日目の記事でした。

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

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

あと、 mo.js いい感じだからみんな使ってみてよ!( vue-mo.js もね!)

◆ ◇ ◆ ◇ ◆

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

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

◆ ◇ ◆ ◇ ◆

12日目の記事 :arrow_right: @yaju さんの Handsontable for Vueを使ってみる

14日目の記事 :arrow_right: @kokky さんの VueとVue Routerで、リダイレクトのない理想の404を目指す

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした