とある地方――、 Vue.js で開発をしているときのお話。
筆者はクリックイベントにアニメーションをつけたく、アニメーションフレームワークをネットで漁っていた。
「お! これいいじゃん」
「 めっちゃかっこいい……。mo.js って言うのか」
おもむろにノート PC のキーボードを叩き始める筆者。
ディスプレイに映し出された検索サイトの入力欄には mo.js Vue
とスムーズに打ち込まれ、タンッという打鍵音とともにページが再描画された。
「 Vue 用のパッケージは……、ほとんどないか」
◆ ◇ ◆ ◇ ◆
ということで、 mo.js を Vue で使いやすいようにプラグイン化して npm リリースした ので、そこで得た知見をまとめました。
🏹 TL;DR
ターゲットとなる読者がわかりづらいのでまとめると、
- インスタンスメソッドをもつプラグインの作成
- Vue コンポーネントをもつプラグインの作成
- Vue カスタムディレクティブをもつプラグインの作成
- それら全部乗せのプラグインの作成
- プラグインの npm パッケージ化
といった感じです。
npm パッケージ化は Vue CLI 3
で行っています。
🔫 できあがったもの
vue-mo.js
という npm パッケージをリリースしました。
✨ デモページ ✨ があるので良かったら見てみてください。
ソースコードも GitHub で公開していますので、興味のある方はどうぞ。
azukisiromochi/vue-mo.js | GItHub
@azukisiromochi/vue-mo.js | npm
🧹 プラグイン開発と npm パッケージ化まで
それでは本題、開発についてです。
類似記事は多いのですが、プラグインとしてインスタンスメソッド、コンポーネント、カスタムディレクティブをまとめてドン! みたいなのは少なかったので、なるべく体系的にまとめてみました。
⭐ インスタンスメソッドをもつプラグイン
どんなの?
Vue にグローバルレベルで機能を追加したものをプラグインといいます。
this.$vuemo.Star({
parent: this.$refs.starParent
})
.play()
のように、ソースコード上で this
( Vue
)から直接、開発したプラグインを参照することができるようになります。
プラグインの作成
プラグインと言ってもいろいろなものがありますが、今回は主に インスタンスメソッド・プロパティを追加 することで、プラグインとして用意したメソッドやプロパティが 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
というオブジェクトを追加していて、 $vuemo
は Burst
という 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 を使っています)
参考
⭐ 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 の設定(エフェクトの種類など)を設定して利用します。
参考
⭐ Vue カスタムディレクティブをもつプラグイン
どんなの?
Vue の標準セットである v-if
や v-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
はディレクティブ引数で、このディレクティブではクリックするたびにイベント発火させるかを判断させるために利用しています。
参考
⭐ それら全部乗せのプラグインの作成
これまで紹介した 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
ディレクトリが作成され、ビルド後の JavaScript 資産などが生成されているはずです。
npm に公開
これはたくさんの方が記事に書かれているので、省略しますが、こちらの記事がわかりやすいと思います。
ちなみに、 npm publish
を実行したときにエラーが返ることがあります。
エラーメッセージを取りそこねましたが、『すでに似たパッケージ名あるよ!』みたいな感じのものです。
npm では、 -
や _
、 .
などを区別せずにチェックしているようで、それを踏まえてパッケージ名を決めましょう。
どうしてもエラーになるパッケージ名を使いたい場合は、パッケージ名を @your-name/your-package
のように npm アカウント名を付与する形で命名して、
$ npm publish --access=public
とコマンドを実行すれば公開することができます。
[solving npm’s hard problem: naming packages | the npm blog]
🙇 おわりに
Vue #2 Advent Calendar 2019 の13日目の記事でした。
もともと Qiita 記事を書くつもりでしたが、ちょうどアドベントカレンダーに空きがあったので差し込みました。
( 2 日前に思い立ったのでギリギリ 💦 )
Composition API
で話題がもちきりななか基本的な内容になりましたが、どなたかの役に立つと嬉しいです
あと、 mo.js いい感じだからみんな使ってみてよ!( vue-mo.js
もね!)
◆ ◇ ◆ ◇ ◆
とある地方――、 Vue.js で開発をしているときのお話。
筆者はクリックイベントにアニメーションをつけたく、アニメーションフレームワークをネットで漁っていた。
プラグイン作るのに夢中で、本来の目的がおざなりになっていることを筆者はまだ知らない――。
◆ ◇ ◆ ◇ ◆
12日目の記事 @yaju さんの Handsontable for Vueを使ってみる
14日目の記事 @kokky さんの VueとVue Routerで、リダイレクトのない理想の404を目指す