【Vue2.x, Vuetify, Nuxt】自作コンポーネントをnpmパッケージとして公開する手順

Vuetifyに依存した自作コンポーネントをnpmパッケージとして公開する手順の備忘録です。
# 背景
今まで自作コンポーネントはファイルごとプロジェクトにコピーして使っていたのですが、新しいコンポーネントを作ったり、改修したりした場合のバージョン管理がずさんになりがち。
npmパッケージとして公開・管理することでより便利になるとは思い試してみました。
vue-cliが何たるかも分からずwebpackから手を付け始めてしまったので、実際に公開できるようになるまで2日、公開したものをインストールして使えるようになるまで更に1日。
これは手順を残しておかなければ、ということでこの記事を作りました。
※結果、vue-cliがwebpackのラッパーなので、vue-cli単体で何とかなります。

# 1. npmアカウントの作成
兎にも角にも、自作コンポーネントをnpmに公開するには、自身のアカウントが必要です。
作成手順自体は難しくないので割愛します。[こちら](https://codelab.website/npm-new-account-local-login/)を参考にどうぞ。

# 2. 自作コンポーネント用のプロジェクトを作成
```
vue create PACKAGE_NAME
```
プロジェクトを作成したいディレクトリ（フォルダ）で上記を実行します。
**PACKAGE_NAME** の部分は適宜変更してください。
VueCLI便利。

# 3. パッケージ化するコンポーネントを作成
```src/components/inputs/MyComponent.vue
<template>
  <v-text-field label="自作コンポーネント" />
</template>
```
パッケージングの流れを確認するためだけであれば何でもOKです。

# 4. エントリポイントとなるファイルを作成
エントリポイントは、インストール先でライブラリとして利用される際に読み込まれるモジュールという認識です。
コンポーネントを利用する際は、
1. コンポーネントを利用するvueファイルごとにimportする（ローカル登録）
1. Vue.use()を利用してコンポーネントを登録する。（グローバル登録）
の2種類があります。
ここでは自作コンポーネントをVue.use()でグローバル登録することを前提としてエントリポイントを作成します。（理由は後述）
```src/build/build.lib.js
// パッケージングしたいコンポーネントをimport（複数OK）
import MyComponent from "../components/inputs/MyComponent.vue";
import hoge from "../components/inputs/Hoge.vue";
import fuga from "../components/inputs/Fuga.vue";

// importしたコンポーネントをオブジェクトに入れておく
const Components = {
  MyComponent,
  Hoge,
  Fuga,
};

// install()を定義する。
// Vue.use(XXX)は、XXX.install()を実行する。
// ここではimportしたコンポーネントをVueにグローバル登録している。
const install = function (Vue) {
  if (install.installed) return;
  install.installed = true;
  Object.keys(Components).forEach((component) => {
    Vue.component(component, Components[component]);
  });
};

const plugin = { install };

// まだ調べ切れていないが、CDNで使用されるときはここが呼ばれる？？？
let GlobalVue = null;
if (typeof window !== "undefined") {
  GlobalVue = window.Vue;
} else if (typeof global !== "undefined") {
  GlobalVue = global.Vue;
}
if (GlobalVue) {
  GlobalVue.use(plugin);
}

// ここが公式や他の投稿と異なる。
// pluginをexport defaultしておかないと、Vue.use(XXX)で上記のinstall()が実行されない。
// export default Components <- これだとダメ
export default plugin;
```
[公式](https://jp.vuejs.org/v2/cookbook/packaging-sfc-for-npm.html "Vue コンポーネントを npm パッケージ化する")を読んで自分がハマったところなのですが、公式のサンプルだとpluginをexportしていないので、Vue.use()してもinstallが実行されません。
pluginはdefault exportしておいて、importしたコンポーネントはnamed exportしておかなければならないのではないだろうか。。。
npmパッケージとして公開してでも再利用したいコンポーネントなのであれば、使用頻度が高いでしょうから、グローバル登録を前提としています。

# 5. package.jsonを編集。
```package.json
  "name": "PACKAGE_NAME",
  "version": "0.1.5",
  "private": false,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "build-bundle": "vue-cli-service build --target lib --name PACKAGE_NAME --formats umd src/build/build.lib.js",
    "lint": "vue-cli-service lint"
  },
  "main": "dist/PACKAGE_NAME.umd.js",
  "files": [
    "dist"
  ],
```
*PACKAGE_NAME* の部分は適宜修正。
privateはfalseに。trueの場合、課金しないとnpmに公開できません。
scriptsにbuildするためのコマンドを「build-bundle」として設定しています。VueCLIを使うので「vue-cli-service build」コマンドでOK。
--targetはライブラリとして登録するため「lib」を、--nameはそのままパッケージ名を、最後のパスには前述したエントリポイントのパスを設定します。
--fomatsは、出力されるファイルの形式を指定します。このオプションを付けないと「common.js」と「umd.js」が両方生成されます。
試してみたのですが、Nuxtで利用する場合は「umd.js」でなければダメでした。
mainはbuildした際に生成されるdist内のファイルのうち、インストール先で実行されるファイルを指定します。
※npm installした場合とCDN利用の場合とで分けることもできますが、ここでは割愛します。
filesにはdistを設定しておけばOKです。依存プラグインなどがある場合は追加記述できるそうです。

# 6. buildします。
```
npm run build-bundle
```
# 7. publishします。
```
npm publish
```
npmにログインしていないとpublishできません。
ログインしていない場合はコンソールで「npm login」を実行しましょう。

# 8. テスト用のプロジェクトを作成し、パッケージをインストールします。
```
npm install PACKAGE_NAME
```

# 9. 動きを確認します。
##### Vueプロジェクトで利用する場合
```main.js
import Vue from "vue";
import App from "./App.vue";
import xxxxx from 'PACKAGE_NAME' // add
import 'PACKAGE_NAME.css' // add

Vue.use(xxxx) // add

new Vue({
  vuetify,
  render: (h) => h(App),
}).$mount("#app");
```
インストールしたパッケージをimportし、Vue.use()を実行。
PACKAGE_NAME.cssもimportしておかないと、cssが機能しない。

##### Nuxtプロジェクトで利用する場合
```plugins/PackageName.js
import Vue from 'vue'
import xxxxx from 'PACKAGE_NAME'
import 'PACKAGE_NAME.css'

Vue.use(xxxxx)
```
```nuxt.config.js
plugins: [
  '~/plugins/PackageName.js',
],
```
パッケージを利用するためのjsファイルをpluginsディレクトリに作成し、nuxt.config.jsのpluginsオプションでこれを指定します。

# CSSファイルをimportしたくない場合
VueCliによるバンドルでは、デフォルトではcssファイルが別ファイルとして生成されます。
この場合、前述のパッケージをライブラリとしてimportしているところで
```
import 'PACKAGE_NAME.css'
```
しないとcssが適用されません。
vue-cliによって生成されるjsファイルにcssを含めたい場合は、vue.config.jsで以下のように設定します。
```diff_javascript
const { defineConfig } = require("@vue/cli-service");
module.exports = defineConfig({
  transpileDependencies: ["vuetify"],
+  css: {
+    extract: false,
+  },
});
```
vue.config.jsでcssの出力を上記のように定義すれば、jsとcssが一つのファイルにまとめられます。
# 参考
https://cli.vuejs.org/guide/cli-service.html#vue-cli-service-build
https://cli.vuejs.org/guide/build-targets.html#library
https://tech-blog.optim.co.jp/entry/2021/04/15/100000
https://passe-de-mode.uedasoft.com/ja/tips/software/frontend/vuetify/vuetify05.html#_2-vue-cli-%E3%81%A6%E3%82%99%E6%9C%80%E5%88%9D%E3%81%8B%E3%82%89%E5%87%BA%E6%9D%A5%E3%81%A6%E3%81%84%E3%82%8B-package-json-%E3%81%AB%E3%80%81%E3%81%84%E3%81%8F%E3%81%A4%E3%81%8B%E3%81%AE%E8%A6%81%E7%B4%A0%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8B
https://jp.vuejs.org/v2/cookbook/packaging-sfc-for-npm.html
勉強させていただきました。ありがとうございます。