2023年に行ったVue3プラグイン開発の備忘録（開発環境構築からnpmjsへの公開まで）

Vue3 + Bootstrap5 + TypeScriptを使ったコンポーザブルな関数を提供するプラグインを開発した際の備忘録になります。
プラグインを開発するための環境はDocker + WSL2で用意しています。
エディタはVSCodeを使用しています。

今回、作成したプラグインのソースは以下になります
GitHub：[imo-tikuwa/vue3-bootstrap5-composables](https://github.com/imo-tikuwa/vue3-bootstrap5-composables)

上記プラグインの開発自体は3週間ほど前に行っていてメモは残していたものの記憶があやふやな箇所が多々あるため、Qiitaに記事としてまとめる用に別途以下のリポジトリも作成し開発を進めながら記事を作成しました。
GitHub：[imo-tikuwa/vue3-plugin-dev-study](https://github.com/imo-tikuwa/vue3-plugin-dev-study)

## 1. Docker環境構築
まず、以下のようなディレクトリ構造でNode.js 18.xのイメージをベースとしたコンテナ環境を用意します。

```:ディレクトリ構造
.
├── .docker/node/Dockerfile
├── .env
├── .env.example
├── .gitignore
└── docker-compose.yml
```

```:.env
COMPOSE_PATH_SEPARATOR=:
COMPOSE_FILE=docker-compose.yml

DEVELOPMENT_PACKAGE_NAME=vue3-plugin-dev-study
```

`DEVELOPMENT_PACKAGE_NAME`というオレオレ環境変数でこれから開発するプラグインのパッケージ名称を渡します。
この値はDockerイメージをビルドする際のARGとしても使用しており、コンテナのWORKDIR（初期ディレクトリ）に設定します。

```yaml:docker-compose.yml
services:
  node:
    build:
      context: ./
      dockerfile: ./.docker/node/Dockerfile
      args:
        - WORKDIR_NAME=${DEVELOPMENT_PACKAGE_NAME}
    volumes:
      - .:/${DEVELOPMENT_PACKAGE_NAME}:cached
    ports:
      - 5173:5173
    tty: true
```

ホストPCのルートディレクトリをコンテナ内の所定の場所にマウントするボリュームの設定やViteの開発サーバーに外からアクセスするためのポートフォワーディングの設定を含みます。

```Dockerfile:.docker/node/Dockerfile
FROM node:18-slim
SHELL ["/bin/bash", "-oeux", "pipefail", "-c"]

ARG WORKDIR_NAME
ENV TZ=Asia/Tokyo

RUN apt-get update -o Acquire::Check-Valid-Until=false -o Acquire::Check-Date=false && \
    apt-get -y --no-install-recommends install ca-certificates && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/* && \
    rm -rf /tmp/* && \
    { \
        echo "alias ll='ls -l --color=auto'"; \
    } >> /home/node/.bashrc

WORKDIR /${WORKDIR_NAME}

USER node
```

.gitignoreでは.envファイルをGitの管理から外す設定のみ行っています
```:.gitignore
.env
```

---
Dockerのイメージビルドとコンテナの起動を行います
```bash:ターミナル
$ docker-compose up -d --build
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2384986/2329bbf2-1ef2-18c1-867f-4092a3c9c211.png)

---
Dockerfile内の`USER node`についてnodeイメージにおけるnodeユーザーはUID=1000のユーザーとなっており、ホストPC（WSL2）の一般ユーザーのUIDと揃うようにしています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2384986/0351a659-a939-5d47-118f-fc5caa7fc129.png)

## 2. Vueの新規プロジェクト作成
`※こちらViteからvueのテンプレートオプションを指定してプロジェクトを作成することも出来るみたいです。この記事の中ではcreate-vueからのプロジェクト作成を行っています。`

まず、以下のコマンドでコンテナ内にアクセスします。
```bash
$ docker-compose exec node bash
node@0d5b94c04953:/vue3-plugin-dev-study$
```

アクセスしたコンテナ内の初期ディレクトリ内がホストPC側と一致していることを確認しています。
```bash
node@0d5b94c04953:/vue3-plugin-dev-study$ ls -al
total 28
drwxr-xr-x 4 node node 4096 Dec  9 10:18 .
drwxr-xr-x 1 root root 4096 Dec  9 10:19 ..
drwxr-xr-x 3 node node 4096 Dec  9 10:16 .docker
-rw-r--r-- 1 node node  104 Dec  9 10:17 .env
-rw-r--r-- 1 node node   83 Dec  9 10:16 .env.example
drwxr-xr-x 7 node node 4096 Dec  9 10:18 .git
-rw-r--r-- 1 node node  317 Dec  9 10:16 .gitignore
-rw-r--r-- 1 node node  268 Dec  9 10:17 docker-compose.yml
```

次に`npm init vue@latest`から対話によってVueの新規プロジェクトを作成するのですが、nodeコンテナ内のカレントには`.gitignore`や`docker-compose.yml`などのファイルが既に存在します。
この状態でプロジェクトを立ち上げようとすると以下に添付する画像のように削除を求められ、拒否するとエラーとなってしまいます。。:sob:

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2384986/e0866297-1279-8adf-2ca3-70d8629ce7b2.png)

そこで新規プロジェクトの作成はコンテナ内の`/tmp`ディレクトリ内で行い、作成したVueプロジェクトの一式をホストとボリューム共有しているディレクトリにコピーするという手順を踏んで構築しています。
`※Dockerfile内で/tmpディレクトリを空にしていたのはこのため`

---
`/tmp`ディレクトリに移動して`npm init vue@latest`からプロジェクトを作成します。

`Project name`はVueのプロジェクトをカレント(`/tmp`)に作成するため`.(ドット記号)`と入力します。

`Package name`で入力した値は生成されるpackage.json内のnameに設定されます。
後で手動で変更することも可能ですが、npmjsでscoped packageとして公開することを想定しているため`[@GitHubユーザー名]/パッケージ名`の入力を行っています。
```bash
node@0d5b94c04953:/vue3-plugin-dev-study$ cd /tmp
node@0d5b94c04953:/tmp$ npm init vue@latest

Vue.js - The Progressive JavaScript Framework

✔ Project name: … .
✔ Package name: … @imo-tikuwa/vue3-plugin-dev-study
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit Testing? … No / Yes
✔ Add an End-to-End Testing Solution? › No
✔ Add ESLint for code quality? … No / Yes
✔ Add Prettier for code formatting? … No / Yes

Scaffolding project in /tmp...

Done. Now run:

  npm install
  npm run format
  npm run dev
```

`Package name`以降のオプションについて、↑のスニペット上だとどちらを選んだかわかりませんが、以下に添付する画像のようにTypeScriptとEslintとPrettierについてYesを選択しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2384986/fb67f7db-cd5d-b0a1-a228-4ac30b67b23a.png)

---
続けて`/tmp`ディレクトリ内に作成したVueプロジェクトを丸ごとホストPCとボリューム共有しているディレクトリに移動します。
`.(ドット記号)`で始まる隠しファイルも含めて丸ごと`/tmp`ディレクトリから移動するには以下のようなコマンド操作が必要でした。

```bash
node@0d5b94c04953:/tmp$ shopt -s dotglob
node@0d5b94c04953:/tmp$ mv /tmp/* /vue3-plugin-dev-study/

↓/tmpディレクトリが空となったことを確認
node@0d5b94c04953:/tmp$ ls -al
total 8
drwxrwxrwt 1 root root 4096 Dec  9 10:49 .
drwxr-xr-x 1 root root 4096 Dec  9 10:19 ..
```

この時点でエディタ側では以下に添付する画像のようにVueプロジェクトが見れているはずです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2384986/b54b0b6f-687a-29a1-bf11-ef1d580bb3f8.png)

---
ホストとボリューム共有を行っているディレクトリに戻り `npm install`などを実施します。
```bash
node@0d5b94c04953:/tmp$ cd /vue3-plugin-dev-study/
node@0d5b94c04953:/vue3-plugin-dev-study$ npm install

added 269 packages, and audited 270 packages in 31s

72 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
```

---
なお、ホストPCに元々存在した`.gitignore`がVueプロジェクトの中に含まれているもので上書きされ、Git管理から外していた`.env`ファイルがコミット対象として表示されてしまっているかと思います。。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2384986/05fa1501-e60d-514e-3424-7cc63cb2c6a1.png)

こちら再度Git管理の対象から外す設定を行います。
```diff:.gitignore
~~~省略~~~
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

*.tsbuildinfo
+
+.env
+
```

## 3. Vite開発サーバー起動
開発環境についてDockerを使っているため、ホストPCからコンテナ内のVite開発サーバーに接続するための設定を行います。
```diff_tsx:vite.config.ts
import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
-  }
+  },
+  server: {
+    host: true
+  }
})
```

こちらの追記を行った後、Viteの開発サーバーを立ち上げます。
```bash
node@0d5b94c04953:/vue3-plugin-dev-study$ npm run dev

  VITE v5.0.7  ready in 350 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: http://192.168.32.2:5173/
  ➜  press h + enter to show help
```

ホストPCのブラウザで http://localhost:5173/ にアクセスし、以下の画面が見れていればOK

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2384986/376aa983-cd96-fa3b-7664-5a4a91674a9f.png)

## 4. 不要なファイルの削除
プラグイン開発前にVueの新規プロジェクトに含まれるHelloWorld.vueなどのファイルについては不要となるので削除します。
不要なファイルは以下のコマンドで削除しました。
```bash
node@0d5b94c04953:/vue3-plugin-dev-study$ rm -rf src/assets/
node@0d5b94c04953:/vue3-plugin-dev-study$ rm -rf src/components/*
```

また、いくつかのファイルについて以下の通り修正しました。
```vue:src/App.vue
<script setup lang="ts">
</script>

<template>
  <div></div>
</template>
```

```ts:src/main.ts
import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')
```

## 5. Bootstrap5の導入
今回はVue3+Bootstrap5のプラグイン開発のデモとしてBootstrap5を導入してBootstrap5を使用したボタンコンポーネントを提供するプラグインを提供する形で進めます。

---
bootstrapはこれから作成するVueプラグインがBootstrap5に依存するプラグインであることからdevDependenciesではなくdependenciesに含める形でインストールします。
また、bootstrapが依存するPopperとsassもインストールします。
```bash
node@0d5b94c04953:/vue3-plugin-dev-study$ npm install bootstrap
node@0d5b94c04953:/vue3-plugin-dev-study$ npm install -D @popperjs/core sass
```

---
bootstrapのSCSSをインポートするための設定を行います。
以下のようなファイルを作成します。
```scss:src/scss/styles.scss
@import 'bootstrap/scss/bootstrap';
```

また、上記ファイルを読み込む設定をmain.tsに追加します。
```diff_tsx:src/main.ts
+import './scss/styles.scss'
+
import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')
```

動作確認用にApp.vueに適当なBootstrap5の確認が行えるHTMLを追加（`※動作確認後に元に戻します`）
```diff_vue:src/App.vue
<script setup lang="ts">
</script>

<template>
-  <div></div>
+  <div class="container mt-2">
+    <h1>Hello, Bootstrap 5!</h1>
+    <p>This is a simple example demonstrating the usage of Bootstrap 5.</p>
+
+    <div class="alert alert-primary" role="alert">
+      This is a primary alert—check it out!
+    </div>
+
+    <button type="button" class="btn btn-success me-2">Success Button</button>
+    <button type="button" class="btn btn-danger">Danger Button</button>
+  </div>
</template>
```

---
Viteの開発サーバーを再度起動してブラウザの表示が以下のようになっていればOK

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2384986/d1c765c9-2d69-7ea0-ace6-b10ad60c9695.png)

## 6. VScode用のPrettier設定
ファイルの編集にVSCodeを使っているので、保管時にPrettierの整形を効かせるための設定を行います。
```json:.vscode/settings.json
{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "prettier.configPath": ".prettierrc.json"
}
```

- `esbenp.prettier-vscode` はVSCodeでPrettierを使うためのプラグインです。
    - Vueのプロジェクト作成時にPrettierについてYesを指定したためVueプロジェクトにおけるPrettierの設定ファイル（`.prettierrc.json`）や`esbenp.prettier-vscode`プラグインの使用を推奨するVSCodeの設定（`.vscode/extensions.json`）は既に含まれているかと思います。
    - エディタの指示に従って`esbenp.prettier-vscode`プラグインをインストール済みであればこの設定を明示することによってファイル保存時にPrettierを使ったフォーマットが行えるようになります。
- VSCode側のPrettierもVueプロジェクトと同じ設定ファイルを参照できるようにするため `prettier.configPath`によってプロジェクトのルートの配置されている`.prettierrc.json`を読み込むようにしています。

---
なお、こちらの修正について`.gitignore`で.vscodeディレクトリのextensions.json以外のファイルについて除外設定がされていました。
以下のように修正し、settings.jsonについてGitの管理対象としました。
```diff:.gitignore
~~~省略~~~
# Editor directories and files
.vscode/*
!.vscode/extensions.json
+!.vscode/settings.json
.idea
*.suo
~~~省略~~~
```

## 7. プラグインとして公開したいコンポーネント作成
以下のようなbootstrap5固有のCSSクラスを施したボタンを1つ表示する単一ファイルコンポーネント(以後、SFC)を作成しました。
```vue:src/components/BaseButton.vue
<template>
  <button type="button" class="btn" :class="[btnSizeClass, btnColorClass]" :disabled="disabled">
    <template v-if="label">{{ label }}</template>
    <slot v-else></slot>
  </button>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import type { ButtonProps } from '../types'

const props = withDefaults(defineProps<ButtonProps>(), {
  size: 'medium',
  outline: false,
  color: 'primary',
  disabled: false
})

const btnSizeClass = computed(() => {
  switch (props.size) {
    case 'small':
      return 'btn-sm'
    case 'large':
      return 'btn-lg'
    default:
      return null
  }
})
const btnColorClass = computed(() => `btn-${props.outline ? 'outline-' : ''}${props.color}`)
</script>
```

```ts:src/types/index.ts
export interface ButtonProps {
  label?: string
  size: 'small' | 'medium' | 'large'
  outline: boolean
  color:
    | 'primary'
    | 'secondary'
    | 'success'
    | 'danger'
    | 'warning'
    | 'info'
    | 'light'
    | 'dark'
    | 'link'
  disabled: boolean
}
```

---
このSFCを使用する側のデモページとしてApp.vueを以下の通り修正しました。
```diff_vue:src/App.vue
<script setup lang="ts">
+import BaseButton from './components/BaseButton.vue'
</script>

<template>
-  <div></div>
+  <div class="container mt-2 text-center">
+    <h1>Hello, This is Vue3 & Bootstrap5 Examples.</h1>
+    <h2 class="mt-4">Button Colors</h2>
+    <div class="row">
+      <div class="col-12">
+        <BaseButton label="Primary" class="me-2" />
+        <BaseButton label="Secondary" :color="'secondary'" class="me-2" />
+        <BaseButton label="Success" :color="'success'" class="me-2" />
+        <BaseButton label="Danger" :color="'danger'" class="me-2" />
+        <BaseButton label="Warning" :color="'warning'" class="me-2" />
+        <BaseButton label="Info" :color="'info'" class="me-2" />
+        <BaseButton label="Light" :color="'light'" class="me-2" />
+        <BaseButton label="Dark" :color="'dark'" class="me-2" />
+        <BaseButton label="Link" :color="'link'" />
+      </div>
+    </div>
+    <h2 class="mt-4">Button Colors (outline)</h2>
+    <div class="row">
+      <div class="col-12">
+        <BaseButton label="Primary" :outline="true" class="me-2" />
+        <BaseButton label="Secondary" :outline="true" :color="'secondary'" class="me-2" />
+        <BaseButton label="Success" :outline="true" :color="'success'" class="me-2" />
+        <BaseButton label="Danger" :outline="true" :color="'danger'" class="me-2" />
+        <BaseButton label="Warning" :outline="true" :color="'warning'" class="me-2" />
+        <BaseButton label="Info" :outline="true" :color="'info'" class="me-2" />
+        <BaseButton label="Light" :outline="true" :color="'light'" class="me-2" />
+        <BaseButton label="Dark" :outline="true" :color="'dark'" class="me-2" />
+        <BaseButton label="Link" :outline="true" :color="'link'" />
+      </div>
+    </div>
+    <h2 class="mt-4">Button Sizes</h2>
+    <div class="row">
+      <div class="col-12">
+        <BaseButton label="Small" :size="'small'" class="me-2" />
+        <BaseButton label="Medium" class="me-2" />
+        <BaseButton label="Large" :size="'large'" />
+      </div>
+    </div>
+    <h2 class="mt-4">Button Disabled</h2>
+    <div class="row">
+      <div class="col-12">
+        <BaseButton label="Disabled" :disabled="true" />
+      </div>
+    </div>
+    <h2 class="mt-4">Button Slot Label</h2>
+    <div class="row">
+      <div class="col-12">
+        <BaseButton>Text111<br />Text222</BaseButton>
+      </div>
+    </div>
+  </div>
</template>
```

---
Viteの開発サーバーを起動して以下に添付する画像のような表示が確認できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2384986/66b006d3-8afb-ec19-21e6-2b171fafd021.png)

## 8. Vueプラグインファイル作成&お試しビルド

まず、↑の手順で作成したSFCをプラグインとして提供するためのプラグインファイルを作成します。

```ts:src/plugins/bootstrapButton.ts
import type { App, Plugin } from 'vue'
import BaseButton from '../components/BaseButton.vue'

export const BootstrapButtonPlugin: Plugin = {
  install(app: App) {
    app.component('BaseButton', BaseButton)
  }
}
```
- Vue本体のPluginという型を参照した`BootstrapButtonPlugin`という名前のオブジェクトを外向けにexportしています
- プラグインとして提供するVueコンポーネントを、使用する側でインポートせずにグローバルに呼び出せるようプラグインのinstall内で定義しています

---
続けて、↑で作成したプラグインファイルとBaseButton.vueを1つのファイルにまとめて再度エクスポートするエントリーファイル(`src/index.ts`)を作成します。
```ts:src/index.ts
import BaseButton from './components/BaseButton.vue'
import { BootstrapButtonPlugin } from './plugins/bootstrapButton'

export { BaseButton, BootstrapButtonPlugin }
```

---
↑で作成したエントリーファイルを読み込み、ライブラリとしてのバンドルを作成する設定を`vite.config.ts`に追加します。
```diff_tsx:vite.config.ts
import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
+import { resolve } from 'node:path'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
  ],
+  publicDir: false,
+  build: {
+    lib: {
+      entry: resolve(__dirname, 'src/index.ts'),
+      name: 'Vue3PluginDevStudy',
+      fileName: 'vue3-plugin-dev-study'
+    },
+    rollupOptions: {
+      external: ['vue', 'bootstrap'],
+      output: {
+        globals: {
+          vue: 'Vue'
+        }
+      }
+    }
+  },
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  },
  server: {
    host: true
  }
})
```

---
↑の`vite.config.ts`の修正によってViteはbuildコマンドについてライブラリモードと呼ばれるモードで動作するようになります（`build.lib`のところ）。

ここまでの状態で試しにビルドを行います。ビルドは`npm run build`から行います。
```bash
node@0d5b94c04953:/vue3-plugin-dev-study$ npm run build

> @imo-tikuwa/vue3-plugin-dev-study@0.0.0 build
> run-p type-check "build-only {@}" --


> @imo-tikuwa/vue3-plugin-dev-study@0.0.0 build-only
> vite build


> @imo-tikuwa/vue3-plugin-dev-study@0.0.0 type-check
> vue-tsc --build --force

vite v5.0.7 building for production...
"createCommentVNode" is imported from external module "vue" but never used in "src/components/BaseButton.vue?vue&type=script&setup=true&lang.ts".
✓ 4 modules transformed.
dist/vue3-plugin-dev-study.js  1.15 kB │ gzip: 0.59 kB
dist/vue3-plugin-dev-study.umd.cjs  1.12 kB │ gzip: 0.64 kB
✓ built in 547ms

node@0d5b94c04953:/vue3-plugin-dev-study$ find ./dist/ -type f   
./dist/vue3-plugin-dev-study.js
./dist/vue3-plugin-dev-study.umd.cjs
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2384986/15dd3e9d-74e2-b407-063b-b01e0bdce0ce.png)

- 先ほど作成したエントリーファイル（`src/index.ts`）を読み込み2つのファイルをdistディレクトリ内に出力しているログが流れています。
- 2ファイルの中にBaseButton.vueの中身に関するコードが含まれていることが確認できますが、TypeScriptの型に関する情報が一切含まれていないことがわかります。。

## 9. TypeScriptの型情報をビルドに含める改修
色々調べた結果、Viteのライブラリモードによるビルドとは別に[vue-tsc](https://www.npmjs.com/package/vue-tsc)というVueのSFCに対応したTypeScriptコンパイラを使ったビルドが必要なことがわかりました。

`※ここの作業内容は私が考えたものではなく以下の記事を参考にした改修を行っています。そのため自分の中でいくつかおまじない(意味を理解していない)的な箇所があります。。`
参考：[How to Create and Publish a Vue Component Library – 2023 Update](https://www.freecodecamp.org/news/how-to-create-and-publish-a-vue-component-library-update/)

---
まず`tsconfig.app.json`について削除
```bash
node@0d5b94c04953:/vue3-plugin-dev-study$ rm tsconfig.app.json
```

次に`tsconfig.json`について以下の通り修正
```json:tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "module": "ESNext",
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "skipLibCheck": true,

    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    // "noEmit": true,
    "jsx": "preserve",

    "outDir": "dist",
    "declaration": true,

    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true
  },
  "exclude": [
    // ライブラリへのバンドルから除外したいファイル
    "src/App.vue",
    "src/main.ts"
  ],
  "include": [
    // ライブラリへのバンドルに含めたいファイル
    "src/**/*.ts",
    "src/components/*.vue"
  ],
  "references": [{ "path": "./tsconfig.node.json" }]
}
```

次に`tsconfig.node.json`を修正
```diff_json:tsconfig.node.json
{
  "extends": "@tsconfig/node18/tsconfig.json",
  "include": [
    "vite.config.*",
    "vitest.config.*",
    "cypress.config.*",
    "nightwatch.conf.*",
    "playwright.config.*"
  ],
  "compilerOptions": {
    "composite": true,
-    "noEmit": true,
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "types": ["node"]
  }
}

```

最後に`package.json`を修正
```diff_json:package.json
  "scripts": {
    "dev": "vite",
-    "build": "run-p type-check \"build-only {@}\" --",
+    "build": "vite build && vue-tsc --emitDeclarationOnly",
    "preview": "vite preview",
-    "build-only": "vite build",
-    "type-check": "vue-tsc --build --force",
+    "type-check": "vue-tsc --noEmit --composite false",
    "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
    "format": "prettier --write src/"
  },
```

---
この修正を施したうえで再度ビルドを実施したところ以下のようになりました。
```
node@0d5b94c04953:/vue3-plugin-dev-study$ npm run build

> @imo-tikuwa/vue3-plugin-dev-study@0.0.0 build
> vite build && vue-tsc --emitDeclarationOnly

vite v5.0.7 building for production...
✓ 4 modules transformed.
dist/vue3-plugin-dev-study.js  1.15 kB │ gzip: 0.59 kB
dist/vue3-plugin-dev-study.umd.cjs  1.12 kB │ gzip: 0.64 kB
✓ built in 454ms

node@0d5b94c04953:/vue3-plugin-dev-study$ find ./dist/ -type f    
./dist/types/index.d.ts
./dist/vue3-plugin-dev-study.js
./dist/index.d.ts
./dist/plugins/bootstrapButton.d.ts
./dist/components/BaseButton.vue.d.ts
./dist/vue3-plugin-dev-study.umd.cjs
```
先ほどは2ファイルしかなかったdistディレクトリ内に何やら色々生えてきました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2384986/28e5b5a9-d5e2-c327-3e30-3fe79d55a235.png)

パッケージに含めたかったBaseButton.vueのpropsに割り当ててるTypeScriptの型情報が出力されてることを確認できました。:wink:

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2384986/716530c7-6698-220a-5ad4-5fdd63e1bd8d.png)

## 10. npmパッケージとして公開するための設定を追加

package.jsonにnpmパッケージとして公開したときに表示する情報を追記
```diff_json:package.json
{
  "name": "@imo-tikuwa/vue3-plugin-dev-study",
-  "version": "0.0.0",
+  "version": "0.0.1",
-  "private": true,
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build && vue-tsc --emitDeclarationOnly",
    "preview": "vite preview",
    "type-check": "vue-tsc --noEmit --composite false",
    "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
    "format": "prettier --write src/"
  },
  "dependencies": {
    "bootstrap": "^5.3.2",
    "vue": "^3.3.10"
  },
  "devDependencies": {
    "@popperjs/core": "^2.11.8",
    "@rushstack/eslint-patch": "^1.3.3",
    "@tsconfig/node18": "^18.2.2",
    "@types/node": "^18.19.2",
    "@vitejs/plugin-vue": "^4.5.1",
    "@vue/eslint-config-prettier": "^8.0.0",
    "@vue/eslint-config-typescript": "^12.0.0",
    "@vue/tsconfig": "^0.4.0",
    "eslint": "^8.49.0",
    "eslint-plugin-vue": "^9.17.0",
    "npm-run-all2": "^6.1.1",
    "prettier": "^3.0.3",
    "sass": "^1.69.5",
    "typescript": "~5.2.0",
    "vite": "^5.0.5",
    "vue-tsc": "^1.8.25"
-  }
+  },
+  "description": "This is Vue3&Bootstrap5 component library development examples.",
+  "files": [
+    "dist"
+  ],
+  "main": "./dist/vue3-plugin-dev-study.umd.cjs",
+  "module": "./dist/vue3-plugin-dev-study.js",
+  "exports": {
+    ".": {
+      "import": "./dist/vue3-plugin-dev-study.js",
+      "require": "./dist/vue3-plugin-dev-study.umd.cjs",
+      "types": "./dist/index.d.ts"
+    }
+  },
+  "types": "./dist/index.d.ts",
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/imo-tikuwa/vue3-plugin-dev-study.git"
+  },
+  "homepage": "https://github.com/imo-tikuwa/vue3-plugin-dev-study"
}
```

## 11. npmjsでアクセストークンを作成
`※npmjsのアカウント作成は2年以上前に行っており一切の記憶がないので省略しています。。`

npmjsのサイトでログイン後、Access Tokensのページより「Granular Access Token」をクリック

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2384986/2ad16daf-d5b9-7a7e-0204-db5de9eaabdb.png)

---
トークン名、説明文、有効期限等、アクセストークンに関する情報を入力

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2384986/d5736c17-a4b5-7c58-1768-38f8af47e374.png)
↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2384986/e8e99be6-c856-5155-5333-88f110243b65.png)
`※この記事内のどこか上の方にも書いた通り、勉強中のプラグインを世間に大々的に公開するのは忍びないので自分のアカウント名以下のパッケージ（scoped package）のみを操作可能とするパーミッションの設定を行っています。`

---
作成したアクセストークンを含む`.npmrc`をプロジェクトのルートに作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2384986/1fdd4d1d-d7c6-035e-d648-7b7c1ace4740.png)

```.npmrc
registry=https://registry.npmjs.org/
init-author-name=[GitHubユーザー名]
init-author-url=[GitHubユーザーのURL]
//registry.npmjs.org/:_authToken=[作成したアクセストークン]
```

---
.gitignoreに`.npmrc`の除外設定を追加
```diff:.gitignore
~~~省略~~~
.env
+.npmrc
```

## 12. npmjsへパッケージを公開
公開はコンテナ内より`npm publish --access=public`で行えます。
念のため公開前に以下の辺りを確認しておくとよいかと思います。

- distディレクトリに公開したいファイルが存在するか（ビルドが済んでいるか）
- package.jsonのパッケージ公開に関する情報に不備はないか（パッケージ名、バージョン等）
- .npmrcのアクセストークンの設定に不備はないか

```
node@0d5b94c04953:/vue3-plugin-dev-study$ npm publish --access=public
npm notice 
npm notice package: @imo-tikuwa/vue3-plugin-dev-study@0.0.1
npm notice === Tarball Contents === 
npm notice 1.7kB README.md                          
npm notice 1.5kB dist/components/BaseButton.vue.d.ts
npm notice 167B  dist/index.d.ts                    
npm notice 87B   dist/plugins/bootstrapButton.d.ts  
npm notice 246B  dist/types/index.d.ts              
npm notice 1.1kB dist/vue3-plugin-dev-study.js      
npm notice 1.1kB dist/vue3-plugin-dev-study.umd.cjs 
npm notice 1.6kB package.json                       
npm notice === Tarball Details === 
npm notice name:          @imo-tikuwa/vue3-plugin-dev-study         
npm notice version:       0.0.1                                     
npm notice filename:      imo-tikuwa-vue3-plugin-dev-study-0.0.1.tgz
npm notice package size:  3.4 kB                                    
npm notice unpacked size: 7.6 kB                                    
npm notice shasum:        5189bc8685eda648cdac157a7b5ca6f8a2a9f71e  
npm notice integrity:     sha512-RXfefijEgR/X+[...]x2xYdbWDOvdiA==  
npm notice total files:   8                                         
npm notice 
npm notice Publishing to https://registry.npmjs.org/ with tag latest and public access
+ @imo-tikuwa/vue3-plugin-dev-study@0.0.1
```

npmjsのサイトに戻り、Packagesのページより自分が公開したパッケージを確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2384986/c1274375-b7a1-3cd4-2681-64344af8eba3.png)

---
無事公開出来ていました。:innocent:
今回作成しnpmjsに公開したプラグインはこちら → [@imo-tikuwa/vue3-plugin-dev-study](https://www.npmjs.com/package/@imo-tikuwa/vue3-plugin-dev-study)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2384986/927da82a-cf7d-d26e-a19a-bb02325aa885.png)

---
npmjsではリポジトリのルートにあるREADME.mdを読み取ってプラグインのインデックスページとして表示してくれるみたいなので、あとはこのファイルでプラグインのインストール方法、使い方などを記載することでプラグインとしての体裁が整うのではないかと思います（`※以下は修正し忘れてVueプロジェクトを作成した頃の情報を公開してしまっているので修正が必要な状態`）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2384986/31f0d5c5-b60a-cf03-38b9-075ce2e0e6b6.png)

## ちなみに
今回`npm init vue@latest`から作成したVueプロジェクトでは標準で@エイリアスを使用したインポートが行えるようになっています。
```ts:vite.config.ts (抜粋)
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  },
```
```json:tsconfig.app.json (抜粋)
{
  "compilerOptions": {
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}
```

こちら開発を進める際に便利な機能ではあるのですが、ライブラリとして提供する兼ね合いで@エイリアスを使用したインポートでソースの開発を進めてしまうと、バンドル後に解決できなくなってしまいました。。
プラグイン開発では@エイリアスを使用すべきではないのか、あるいはビルド時に解決する方法があるのか、よくわかっていません。。:disappointed_relieved:

## よくわかってないところ
- tsconfigのいろいろ（分からないことが多すぎて言語化できない。。）
- 開発したプラグインをnpmjsに公開する前に開発環境内でインストールしての確認手順

## 参考リンク
- [プラグイン | Vue.js](https://ja.vuejs.org/guide/reusability/plugins)
- [How to Create and Publish a Vue Component Library – 2023 Update](https://www.freecodecamp.org/news/how-to-create-and-publish-a-vue-component-library-update/)
- [Viteでの使用～Bootstrap5設置ガイド](https://bootstrap-guide.com/getting-started/vite)
- [本番環境用のビルド | Vite](https://ja.vitejs.dev/guide/build.html#library-mode)