Vite + React + Tailwind CSSでUIライブラリを作ってみる

この記事は[ファンタアドベントカレンダー2023](https://qiita.com/advent-calendar/2023/funteractive-inc)の5日目です。

現在参加中のプロジェクトでUIライブラリを作成してnpmで配信してみたい！という話になりました。

この記事では、その環境構築手順を共有します

なお本記事で掲載した一部コードを含む全体を[GitHubレポジトリ](https://github.com/yoheeeei/yhi-component-lib)で公開しています。

## 想定読者

- React * Vite * Tailwind CSSを使ったUIライブラリを実装したい方
- 自作のコンポーネントをパッケージで配信してみたい方

## 技術スタック

本記事で使用する主な技術スタックは以下の通りです

- React
- Vite
- TypeScript
- Tailwind CSS

## 1. 初期構築

以下のコマンドでViteのreact-tsテンプレートを用いてプロジェクトを作成します

```bash
yarn create vite . --template react-ts
```

パッケージをインストールする

(yarn.lockが無いよ！とエラーが出るため空のファイルを作成しています)

```bash
touch yarn.lock
yarn
```

## 2. Tailwind CSSの導入

スタイリングはtailwindを使いたいのでパッケージをインストールします

```bash
yarn add -D tailwindcss postcss autoprefixer
```

以下のコマンドでプロジェクトにtailwindを導入します

```bash
yarn tailwindcss init -p
```

この時生成されるpostcss.config.jsの内容はvite.config.tsに移行可能なので移してしまいます

tailwind.config.jsを書き換えます。

この際に`tw_` など任意のプレフィックスをつけることができます。
必要に応じて設定してください。

型がある方が補完効いたり、エディタでエラー表示してくれたり何かと安心なので `.ts` に変えてしまいました。

```tsx
import type { Config } from "tailwindcss";
export default {
  content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
  theme: {
    extend: {},
  },
  plugins: [],
} satisfies Config;
```

プロジェクト初期構築時に生成された`src/index.css` でtailwindのcssを読み込みます

詳しくは[公式ドキュメント](https://tailwindcss.com/docs/preflight)を参照してください

**src/index.css**

```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

## 3. ビルド設定周りを修正

### vite.config.ts

2でpostcss.config.jsを削除したのでこちらに記述します

```tsx
css: {
  postcss: {
		plugins: [tailwindcss, autoprefixer]
	}
}
```

あとはビルド周りも追加します

```tsx
build: {
  lib: {
    entry: resolve(__dirname, "src/index.ts"),
    name: formattedName,
    formats: ["es", "umd"],
    fileName: (format) => `${formattedName}.${format}.js`,
  },
}
```

最終的に以下のようなコードになりました

```tsx
/// <reference types="vitest">
import { resolve } from "path";
import react from "@vitejs/plugin-react";
import autoprefixer from "autoprefixer";
import tailwindcss from "tailwindcss";
import { defineConfig } from "vite";
import dts from "vite-plugin-dts";
import { name } from "./package.json";

const formattedName = name.match(/[^/]+$/)?.[0] ?? name;

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    react(),
    dts({
      insertTypesEntry: true,
    }),
  ],
  css: {
    postcss: {
      plugins: [tailwindcss, autoprefixer],
    },
  },
  build: {
    lib: {
      entry: resolve(__dirname, "src/index.ts"),
      name: formattedName,
      formats: ["es", "umd"],
      fileName: (format) => `${formattedName}.${format}.js`,
    },
    rollupOptions: {
      external: ["react", "react/jsx-runtime", "react-dom", "tailwindcss"],
      output: {
        globals: {
          react: "React",
          "react/jsx-runtime": "react/jsx-runtime",
          "react-dom": "ReactDOM",
          tailwindcss: "tailwindcss",
        },
      },
    },
  },
});
```

この時 `path` が読み込めずエラーになる場合は以下のコマンドで型情報を追加してあげると解決します

```bash
yarn add -D @types/node
```

### tsconfig.json

```json
{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "skipLibCheck": true,

    /* Bundler mode */
    "allowSyntheticDefaultImports": true,
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",

    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,

    "allowJs": false,
    "esModuleInterop": false,
    "moduleResolution": "Node"
  },
  "include": ["src"],
  "references": [{ "path": "./tsconfig.node.json" }]
}
```

### tsconfig.node.json

```json
{
  "compilerOptions": {
    "composite": true,
    "skipLibCheck": true,
    "module": "ESNext",
    "moduleResolution": "bundler",
    "allowSyntheticDefaultImports": true
  },
  "include": ["vite.config.ts", "package.json"]
}
```

## 7. package.jsonの修正

以下のエントリポイント周りの設定をpackage.jsonに追加します

```json
"main": "./dist/yhi-component-lib.umd.js",
"module": "./dist/yhi-component-lib.es.js",
"types": "./dist/index.d.ts",
"license": "MIT",
"exports": {
  ".": {
    "types": "./dist/index.d.ts",
    "import": "./dist/yhi-component-lib.es.js",
    "require": "./dist/yhi-component-lib.umd.js"
  },
  "./dist/style.css": "./dist/style.css"
},
"files": [
  "dist"
],
```

`scripts` にビルドコマンドを追加します

```json
"scripts": {
  // ...
	// ↓追加
	"build": "tsc && vite build",
  // ...
},
```

## コンポーネントの追加

src下にコンポーネントを作成します

コンポーネントは1つのディレクトリにまとめたかったため作成した`components` ディレクトリ下にまとめました

**src/components/Button/Button.tsx**

```tsx
import { ButtonProps } from "./type";

export const Button = (props: ButtonProps) => {
  return <button {...props} className="bg-blue-500 hover:bg-blue-600 px-5 py-2 rounded-lg text-sm text-white " />;
};
```

**src/components/Button/type.ts**

```tsx
import { ComponentProps } from "react";

export type ButtonProps = Pick<ComponentProps<"button">, "children" | "onClick">;
```

**src/components/Button/index.ts**

```tsx
export * from "./Button";
```

src下でCSSと作成したコンポーネントをexportします

**src/index.ts**

```tsx
import "./index.css";
export * from "./components";
```

## 8. ビルド

以下のコマンドでパッケージのビルドを実行します

```bash
yarn build
```

`dist` フォルダ下にファイルが生成されていれば成功です

## 9. パッケージ公開

npmへのログインが完了していなければ認証情報を入れておきましょう

```bash
yarn npm login
```

以下のコマンドでnpmへパッケージを公開できます

```bash
npm publish
```

## 10. 動作確認

作成したコンポーネントは以下のように使用することができます

```tsx
import { Button } from "yhi-component-lib"

export const SamplePage = () => {
  return (
    <div>
      <Button>ボタンA</Button>
      <Button>ボタンB</Button>
      <Button>ボタンC</Button>
    </div>
  )
}
```

**この際、ビルド時に出力したCSSを読み込むのを忘れないようにしましょう(自分は忘れてスタイルが当たらず1日潰しました)**

以下のように出力されたCSSをインポートする文を全体で読み込むスタイルに含めておくと良いと思います

```css
@import "yhi-component-lib/dist/style.css"
```

## おわりに

実際にはこの記事の内容に加えて以下のようなこともやっているので、この辺りもどこかで書けたらいいなと思います

- Storybookの導入
- Vitestでテスト実行
- GitHub Actions上でnpmのパッケージ公開自動化
- Hygenを導入してコンポーネント作成をある程度自動化

明日は筋肉系デザイナーの朝妻さんが担当してくれます！お楽しみに！！！！

## 参考記事・レポジトリ

- https://miyauchi.dev/ja/posts/lib-vite-tailwindcss/
- https://github.com/IgnacioNMiranda/vite-component-library-template