【個人開発】Vue3のカレンダーライブラリを作った


今回は、Vue 3を使ってカスタムデートピッカーライブラリ「vue3-datepick」を作成しました。なぜこのライブラリを作ったのか、どういった技術を選定したのかを、細かい実装例や設計思想とともに詳しく解説していきます。最終的にはnpmに公開し、使える形に仕上げました。  
リポジトリはこちら: [vue3-datepick GitHubリポジトリ](https://github.com/FAL-coffee/vue3-datepick)。

## 作ったもの

vue3-datepickは、Vue 3コンポーネントとして簡単にインストールして利用できるカレンダーライブラリです。次の機能を持っています:

- シンプルで直感的なUI
- 日付フォーマットのカスタマイズ (`YYYY-MM-DD`, `DD/MM/YYYY` など)
- 特定の日付を無効化
- 日本語を含む多言語対応

## 作った理由

vue2 PJのvue3 リプレースにおいて、vue2で使用していたカレンダーライブラリと同じ使用感のものがvue3になかったためです。
最近はあまりコミット出来ていませんが、現状一人で開発・メンテしています。

### 使用例

基本的な使い方は以下の通りです:

```vue
<template>
  <DatePick v-model="selectedDate" :disable-dates="disableDates" />
</template>

<script setup>
import { ref } from 'vue';
import DatePick from 'vue3-datepick';

const selectedDate = ref(null);
const disableDates = ['2024-01-01', '2024-12-25'];
</script>
```

上記のように、`v-model`で日付の双方向バインディングを行い、特定の日付（例: 祝日）を無効化することができます。

#### Props 説明

| Prop Name          | Type                           | Default                                                                                     | Description                                                                                                                                                                     |
|--------------------|--------------------------------|---------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **`modelValue`**   | `string`                      | -                                                                                           | Vue 3での双方向バインディングを実現するための規定の`Props`です。`vue3-datepick`では`dayjs`のフォーマットに基づいた年月日文字列を使用します。                                            |
| **`yearFirst`**    | `boolean`                     | `true`                                                                                      | 年と月の表示順序を入れ替えるオプションです。`true`で年を先に表示、`false`で月を先に表示します。                                                                                 |
| **`months`**       | `string[]`                    | `['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']` | 各月の表示文字列を配列で渡すことが可能です。日本語では「1月」「01月」「一月」などの表示フォーマットが多岐に渡るため、カスタマイズ性を重視しています。どの言語圏でも違和感なく使用できます。       |
| **`startWeekOnSunday`** | `boolean`                     | `false`                                                                                     | 週の開始日を日曜日とするかどうかを設定するオプションです。                                                                                                                       |
| **`weekdays`**     | `string[]`                    | `['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']`                                         | 曜日を文字列配列で上書き可能です。例えば、日本語では「月」「火」などのカスタマイズができます。                                                                                     |
| **`disabled`**     | `boolean`                     | `false`                                                                                     | 入力ボックスを無効化するオプションです。                                                                                                                                        |
| **`placeholder`**  | `string`                      | -                                                                                           | 入力ボックスが空の場合に表示されるプレースホルダー文字列です。                                                                                                                     |
| **`displayFormat`** | `string`                      | `yyyy.MM.dd`                                                                                | `dayjs`のフォーマットに基づいた文字列を指定できます。入力ボックスの表示や、emitされる日付文字列もこのフォーマットに従います。                                                                 |
| **`isDateDisabled`** | `(date: Date) => boolean`      | -                                                                                           | 各日付の選択を無効化するためのコールバック関数を渡すことが可能です。                                                                                                               |
| **`yearContent`**  | `string`                      | -                                                                                           | 年表示部の接尾辞です。                                                                                                                                                         |


## 各技術要素の選定理由

### Vite

ビルドツールとして[Vite](https://vitejs.dev/)を選定しました。開発サーバーの立ち上がりが速く、ホットリロードも軽快です。さらに、Vue 3との組み合わせがシンプルで、パフォーマンスも向上しました。

```bash
npm create vite@latest vue3-datepick --template vue
cd vue3-datepick
yarn install
yarn dev
```

上記のコマンドで即座に開発環境が整います。

### VitePress

ドキュメント生成には[VitePress](https://vitepress.vuejs.org/)を採用しました。VueコンポーネントとMarkdownファイルの統合が容易で、プロジェクトのドキュメンテーションがスムーズに進行できます。特に、Viteとの親和性が高く、シンプルな構成で済むのが魅力です。

簡単な設定でおしゃれでいい感じのドキュメントを簡単に作成・ホスティングできます。

```ts
import { defineConfig } from 'vitepress'

export default defineConfig({
  title: 'vue3-datepick',
  description: 'A simple and customizable date picker for Vue 3.',
  themeConfig: {
    nav: [
      { text: 'Home', link: '/' },
      { text: 'Guide', link: '/guide/install' },
      { text: 'API', link: '/api/' },
      { text: 'GitHub', link: 'https://github.com/FAL-coffee/vue3-datepick' },
    ],
    sidebar: {
      '/guide/': [
        {
          text: 'Guide',
          items: [
            { text: 'Installation', link: '/guide/install' },
            { text: 'Getting Started', link: '/guide/getting-started' },
            { text: 'Customization', link: '/guide/customization' },
            { text: 'Examples and Demos', link: '/guide/examples' },
          ],
        },
      ],
      '/api/': [
        {
          text: 'API Reference',
          items: [{ text: 'API', link: '/api/' }],
        },
      ],
    },
  },
})

```

![スクリーンショット 2024-11-19 13.44.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1869061/c35a876c-a19a-f82b-4737-83604fbff055.png)


https://vue3-datepick-docs.vercel.app/

### Vitest

テストには、軽量かつ高速な[Vitest](https://vitest.dev/)を使用しました。Vueコンポーネントのテストに最適で、既存のJestの知識もそのまま活かせます。

以下は日付の無効化機能のテストコードです。

```ts
import { mount } from '@vue/test-utils';
import { expect, test } from 'vitest';
import DatePick from './DatePick.vue';

test('should disable specific dates', () => {
  const wrapper = mount(DatePick, {
    props: {
      disableDates: ['2024-01-01'],
    },
  });

  expect(wrapper.find('[data-date="2024-01-01"]').classes()).toContain('disabled');
});
```

特定の日付を無効化するロジックが正しく動作するかを確認できます。

### SCSS + BEM

スタイル管理には[SCSS](https://sass-lang.com/)を用い、クラス名にはBEM（Block Element Modifier）を採用しました。これにより、スタイルの再利用性を高めつつ、可読性を保つことができます。

```scss
.date-picker {
  &__input {
    padding: 10px;
    border: 1px solid #ccc;
  }
  &__calendar {
    display: flex;
    flex-direction: column;
  }
  &__day--disabled {
    color: #ddd;
    pointer-events: none;
  }
}
```

BEMでの構造化により、大規模なプロジェクトでも拡張しやすくなっています。

## CI / CD

### Release → npm publish

リリース作業は[GitHub Actions](https://github.com/features/actions)を使って自動化しています。特定のタグをプッシュするだけで、npmに自動的にパッケージが公開されます。

```yaml
name: Publish

on:
  push:
    tags:
      - 'v*.*.*'

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - run: npm ci
      - run: npm publish --access public
```

これにより、リリースフローの自動化が実現し、ミスが減りました。

https://qiita.com/FAL-coffee/items/93ae27c8e2881158b34c

### Test, Lint, Stylelint, and Prettier Checks

コードの品質を維持するために、テストやLint、Stylelint、PrettierによるチェックをCIに組み込んでいます。すべてのプルリクエストに対して自動的にこれらのツールが走り、クリーンなコードベースを保ちます。

```yaml
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - run: npm ci
      - run: npm run lint
      - run: npm run prettier --check
```

これでコードの一貫性とスタイルの整合性を保つことができます。

### Vercel（VitePress）

[Vercel](https://vercel.com/)を使って、VitePressで作成したドキュメントをホスティングしています。
Vercelはデプロイが簡単で、プレビュー環境も提供してくれるため、ドキュメントの更新がスムーズに行えます。

## まとめ

Vue 3を用いて、軽量かつカスタマイズ性の高いデートピッカーライブラリを開発しました。Vite、VitePress、Vitest、SCSS + BEMといった技術スタックを組み合わせ、快適な開発体験とモジュール性を重視した設計です。さらに、GitHub Actionsを用いたCI/CDパイプラインにより、リリース作業の自動化とコード品質の維持も実現しました。

今後も継続的に改善していく予定です。興味があればぜひ[vue3-datepick](https://github.com/FAL-coffee/vue3-datepick)をチェックしてみてください！