Vue.js + Vuetify + fontawesome-freeで作った独自コンポーネントをnpm公開してみた

どうも銀澪です。
今回はVue.js製コンポーネントをnpmライブラリ化して公開するまでの備忘録になります。

# 公開したコンポーネント

- [vue-common-confirm](https://www.npmjs.com/package/@gn5r/vue-common-confirm)
- [GitHub](https://github.com/gn5r/vue-common-confirm)
- [デモページ](https://gn5r.github.io/vue-common-confirm/)

# 目次
1. [開発環境とバージョン](#開発環境とバージョン公開時点)
2. [公開するVueアプリの開発](#公開するvueアプリの開発)
  - [ひな形を作成](#ひな形を作成)
  - [依存関係の追加](#依存関係の追加)
3. [ディレクトリ構成](#ディレクトリ構成)
4. [npm公開するための準備](#npm公開するための準備)
  - [公開するコンポーネントをexport](#公開するコンポーネントをexport)
  - [package.jsonを整える](#packagejsonを整える)
  - [npmアカウントの作成](#npmアカウントの作成)
5. [コンポーネントを公開する](#コンポーネントを公開する)
6. [公開したコンポーネントを使用する](#公開したコンポーネントを使用する)
  - [インストール](#インストール)
  - [使い方](#使い方)
7. [mixinスクリプトを独自に書きたい](#mixinスクリプトを独自に書きたい)
8. [【番外編】Github Pagesを利用してデモページの作成](#番外編github-pagesを利用してデモページの作成)

# 開発環境とバージョン(公開時点)

|名称|バージョン|
|:--|:--|
|Windows 10 Home 64bit|1909|
|Node.js|12.13.1|
|npm|6.14.4|
|@vue/cli|4.1.2|
|Vue|^2.6.11|
|Vuetify|^2.2.11|
|@fortawesome/fontawesome-free|^5.13.0|
|VisualStudio Code|1.44.2|

# 公開するVueアプリの開発

## ひな型を作成
`vue create vue-common-confirm`

## 依存関係の追加
`npm i @fortawesome/fontawesome-free`
`vue add vuetify`
公開時点ではdevDependenciesに移動させていますが、VSCodeのスニペットを有効にさせたいので開発中はdependenciesに記述しています。

# ディレクトリ構成

```
vue-common-confirm
├─.browserlistrc
├─.eslintrc.js
├─.gitignore
├─babel.config.js
├─LICENSE
├─package-lock.js
├─package.json
├─README.md
├─vue.config.js
|
├─lib
|  └─mixin.js // 公開するmixin用のスクリプト
├─public
|  
└─src
    ├─assets
    ├─components
    │  ├─common
    |  |  ├─HeaderBar.vue
    |  |  └─FooterBar.vue
    |  ├─Confirm.vue // 公開するコンポーネント
    |  └─Sample.vue
    ├─plugins
    |     └─ vuetify.js
    ├─App.vue
    ├─main.js
    └─index.js  // package.jsonのmainになるスクリプト。ここで公開するコンポーネントをexportしている
```

あまり気にしなくていいファイルは記述していません。公開時点でのディレクトリ構成はこの様になっています。
`vue create`後に作成されたファイルやディレクトリ以外で違うものは、デモページを作るにあたって自前のHeader,FooterとSampleコンポーネントだったり公開するコンポーネントとスクリプトが増えているくらいです。
mixin用のスクリプトを`import mixin from "@gn5r/vue-common-confirm/lib/mixin"`と読み込ませたいので`lib`ディレクトリをrootに追加しています。

# npm公開するための準備

## 公開するコンポーネントをexport
`src/`下に`index.js`を作成し以下のように記述

```js:index.js
import Confirm from "@/components/Confirm";

export function install(Vue) {
  if (install.installed) return;
  install.installed = true;
  Vue.component("Confirm", Confirm);
}

export default Confirm;
```

install functionは一応記述しています。Vue.use()を使ってライブラリを読み込んだ時に、自動的にコンポーネントをグローバル登録しています。

## package.jsonを整える
`vue create`後のpackage.jsonでは足りない設定があったりビルドコマンドの追加などがあるのでそれを足していきます

```json:package.json
{
  "name": "@gn5r/vue-common-confirm",
  "version": "0.1.5",
  "private": false, // デフォルトはtrueになっているのでfalseにする
  "description": "Vue.js用共通ダイアログコンポーネント",
  "author": "gn5r", // 追加
  "scripts": {
    "serve": "vue-cli-service serve",
    "build:lib": "vue-cli-service build --target lib --name vue-common-confirm src/index.js", // コンポーネントをライブラリ化するためのビルドコマンドを追加。
    "lint": "vue-cli-service lint",
    "build": "vue-cli-service build" // デモページ用に残しておく
  },
  "main": "dist/vue-common-confirm.common.js", // ビルド後に生成されたスクリプトを設定することで、公開したコンポーネントを利用できる
  "files": [
    "dist", // 公開するディレクトリを記述。distは必要ないかもしれない?
    "lib" // mixin用のスクリプトを公開させたいので列挙
  ],
  "dependencies": {}, // 使用側の環境を弄りたくないので依存関係は使用側に依存させることにしています(バージョン違いのnode_modulesが生成されても問題ないのであればこっちに移しても良いかも?)
  "devDependencies": {
    "@fortawesome/fontawesome-free": "^5.13.0",
    "@vue/cli-plugin-babel": "^4.3.0",
    "@vue/cli-plugin-eslint": "^4.3.0",
    "@vue/cli-service": "^4.3.0",
    "babel-eslint": "^10.1.0",
    "eslint": "^6.7.2",
    "eslint-plugin-vue": "^6.2.2",
    "sass": "^1.19.0",
    "sass-loader": "^8.0.0",
    "vue-cli-plugin-vuetify": "^2.0.5",
    "vue-template-compiler": "^2.6.11",
    "vuetify-loader": "^1.3.0",
    "core-js": "^3.6.4",
    "vue": "^2.6.11",
    "vuetify": "^2.2.11"
  },
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "https://github.com/gn5r/vue-common-confirm.git"
  },
  "homepage": "https://gn5r.github.io",
  "keywords": [
    "vue",
    "confirm",
    "vue-common-confirm"
  ],
  "publishConfig":{
    "access":"public"
  }
}
```

## npmアカウントの作成
npmアカウントを作っていない場合は[npmjs](https://www.npmjs.com/)にてアカウントを作成しておいてください
作成済み or 作成後は`npm login`してください

# コンポーネントを公開する
git連携している場合、変更をコミットしていないと`npm publish`出来ないのでコミットします
`git add .(個別に設定したいなら列挙)`
`git commit -m "first commit"`
`git tag -a v0.1.0 -m "初回リリース"`
`git push main(remote名がmainなので、適宜変更) master && git push main v0.1.0`
コミットと同時にタグ付けをしています。(初めてタグ付けしましたが便利ですね、これ。リリース履歴が見れる)

変更をすべてcommit&pushしたらnpmに公開します
`npm publish --access=public`
~~`--access=public`オプションを付与しないとエラーが出てくるようです。回避方法とか無いのかね？~~
scope moduleを公開する時は`--access=public`オプションを付与するか
`package.json`に以下の設定を記述(定義)すれば`npm publish`できるようです

```json:package.json
  "publishConfig":{
    "access":"public"
  }
```

以上で公開するコンポーネントの準備からnpm registryに公開するまでの備忘録となります。

# 公開したコンポーネントを使用する

## インストール
`npm i @gn5r/vue-common-confirm`

## 使い方
READMEに書いていますけど一応この記事にも載せておきます

```vue:Sample.vue
<template>
  <div>
    <button @click="addData">追加</button>
    <confirm
      :dialog.sync="confirmObj.dialog"
      :title="confirmObj.title"
      :titleIcon="confirmObj.titleIcon"
      :titleColor="confirmObj.titleColor"
      :message="confirmObj.message"
      :buttons="confirmObj.buttons"
    />
  </div>
</template>

<script>
import Confirm from "@gn5r/vue-common-confirm";
import mixin from "@gn5r/vue-common-confirm/lib/mixin";

export default {
  name: "sample",
  mixins: [mixin],
  data: () => ({}),
  methods: {
    addData() {
      // ~~~~~なんか処理
      // ~~~~~なんか処理
      this.confirm("データを追加しますか?", () => {
        // ダイアログで"はい"を押した後にコールバック関数が実行されるので
        // コールバック関数を記述する。または引数で関数を渡しておく
      });
    },
  },
  components: {
    Confirm,
  },
};
</script>
```

グローバル登録しようと`Vue.mixin(mixin)`したら`error function`が重複してエラー吐いたのでどうしようか...
エラー発生時のダイアログ呼び出しは`this.error('エラーが発生しました。')`的な設計の元コーディングしていたので代案があれば教えてくれ…

# mixinスクリプトを独自に書きたい
提供しているmixinスクリプトに不満がある場合は以下のサンプルを参考に独自で作ることもできます


```javascript:custom.js
export default {
  data:()=>({
    // Confirm用オブジェクト
    confirmObj: {
      dialog: false,
      title: "",
      titleIcon: "",
      titleColor: "",
      message: "",
      buttons: [
        {
         text:"ボタンテキスト",
         color:"ボタンカラー",
         function:"ボタンを押した時実行される関数"
        }
      ],
    },
  }),
  methods:{
    // サンプル関数
    sample(message){
      this.confirmObj.title="ダイアログタイトルです";  // ダイアログヘッダーに表示されるタイトル
      this.confirmObj.titleIcon="fas fa-edit";// fontawesomeやmdi-xxxなどが使えます
      this.confirmObj.titleColor="info" // vuetifyで提供されているカラーを設定できます
      this.confirmObj.message = message; // 引数のメッセージをそのまま代入
      this.confirmObj.buttons = [
        {
         text:"ぼたん",
         color:"success", // vuetifyで提供されているカラーを設定
         function:this.close  // クリックイベントで実行される関数を渡す。
        }
      ];
     this.open();
    },
    
    // ダイアログオープン
    open(){
      this.confirmObj.dialog = true;
    },

    // ダイアログクローズ
    close(){
      this.confirmObj.dialog = false;
    }
  }
}
```

confirmObjじゃなくてもいいけど、mixinした場合は`<confirm />`のpropsに渡すオブジェと同じじゃないとundefinedになるので注意
あと注意してほしいのがボタン部分で、オブジェクト配列になっています。

```vue:Confirm.vue
<v-btn v-for="(button, index) in buttons" :key="index" :color="button.color" @click="button.function">
  {{ button.text }}
</v-btn>
```

この様な書き方をしているので、必要なパラメータがないとundefinedになります(indexパラメータは必要ないです。v-for時のみ使っています)

# 【番外編】Github Pagesを利用してデモページの作成
デモページの出力ディレクトリは被りたくないので設定ファイルを弄ります

```javascript:vue.config.js
module.exports = {
  transpileDependencies: ["vuetify"],
  //開発中時の設定
  devServer: {
    //ポートを80で起動
    port: 80,
  },
  // Demo成果物出力ディレクトリ名
  outputDir: "docs", // こいつを追記

  // 本webアプリのurl的なやつ
  publicPath: "./",  // これもないとエラーが出た気がする

  // 画像ファイルなどにハッシュ値を付与しない
  filenameHashing: false,
};
```

`git checkout -b gh-pages`を実行し、Github Pages用ブランチを作成
`package.json`のmainパラメータを一時的に消して`npm run build`を実行しデプロイ準備
`git add docs && git commit -m "デモページデプロイ"`
`git subtree push --prefix docs/ main gh-pages`
`git checkout master && git branch -D gh-pages`

これで`gh-pages`に対して`docs`配下のファイルをcommit&pushできます