Vite で TypeScript + React のコンポーネントライブラリを作成してみる
はじめに
Vite のライブラリモードを使ってnpm install
できるコンポーネントライブラリを作ってみたかったのでやってみた。コンポーネントを作成する時に Storybook を利用した形の記事が日本語記事で発見できなかったので記事を書いてみることにした。ライブラリを作ってみたことがない中、ChatGPT に教わりつつ検索をしながら進めていったので間違っている場所があるかもしれないが、その際は指摘してくれるとありがたい。
なお、この記事は下記のテンプレートを参考にした。一度中身を覗いてみると良い。
作ってみるもの
極めてシンプルなボタンコンポーネントだけを用意したライブラリを作ってみる。
今回作るボタンコンポーネント(画像は Storybook 上)
ローカル環境の準備
開発環境として Node.js が必要だが、それを直接インストールするのではなく node のバージョン管理ツールであるvolta
を導入しておく。特に様々なプロジェクトを PC 内に同居させているとバージョン管理が後々大変になるので導入しておくと良い。
React のプロジェクト準備
ここからどこかにフォルダを作ってそこにプロジェクトを作成するが、実際には GitHub などでリポジトリを作ってgit clone
してきてから行った方が良い。
プロジェクトの作成
TypeScript + React + Vite のプロジェクトを作成する。ありがたいことに Vite にはそれを作成する公式コマンドが用意されているのでそれを使う。どこかに今回のプロジェクトの起点とあるフォルダを作成し、そのフォルダの中で以下のようなコマンドを叩く。
npm create vite@latest . -- --template react-swc-ts
npm install
場合によっては対話モードが開くかもしれないが、プロジェクト名をカレントディレクトリ.
に指定し、React → TypeScript + SWC を選べば良い。
Storybook の導入
以下のコマンドを叩くと Storybook の導入ができる。
npx sb init --builder=vite
これによって Storybook のテンプレートが生成され、.storybook
やsrc/stories
フォルダが追加されるはずである。そして自動的に Storybook の画面が表示されるだろう。もし Storybook を触ったことがない人はここで少し触れてみると良い。
ライブラリ用にファイルを再構成
これで必要なものは導入できたので、次は以下のように構成を変える。npm create vite
で作成されたpublic
フォルダやindex.html
等を削除し、src
の中身はvite-env.d.ts
のみ残して大胆に全部消した。
$ tree -a -I "node_modules|.git"
.
├── .eslintrc.cjs
├── .gitignore
├── .storybook
│ ├── main.ts
│ └── preview.ts
├── README.md
├── package-lock.json
├── package.json
├── src
│ ├── components
│ │ ├── Button
│ │ │ ├── Button.stories.tsx
│ │ │ ├── Button.tsx
│ │ │ └── index.ts
│ │ └── index.ts
│ ├── index.ts
│ └── vite-env.d.ts
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
今回はコンポーネントを Storybook 上で開発したいので、以下のようにpackage.json
を書き換える。
{
...
"scripts": {
+ "dev": "storybook dev -p 6006",
- "dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
- "preview": "vite preview",
- "storybook": "storybook dev -p 6006",
- "build-storybook": "storybook build"
},
...
}
これで以下のコマンドから Storybook が開くようになった。
npm run dev
コンポーネントライブラリの作成
Button コンポーネントの作成
今回用意するのはとてもシンプルなボタンコンポーネントである。(ChatGPT3.5 くんに書いてもらった。)
import React from "react";
export type ButtonProps = {
label: string;
onClick?: () => void;
};
export const Button: React.FC<ButtonProps> = ({ label, onClick }) => {
return <button onClick={onClick}>{label}</button>;
};
Story の用意
stories は以下のように用意した。なお Storybook は v7 を使用しており、記法は CSF3 を利用している。
import { Meta, StoryObj } from "@storybook/react";
import { Button } from "./Button";
const meta: Meta<typeof Button> = {
title: "Components/Button", // 省略可
component: Button,
};
export default meta;
export const Default: StoryObj<typeof Button> = {
args: {
label: "Click me",
},
};
export const WithOnClick: StoryObj<typeof Button> = {
args: {
label: "Click me with onClick",
onClick: () => alert("Button clicked!"),
},
};
これで一度npm run dev
で Storybook を開いて確認してみよう。http://localhost:6006を見に行くとButton
が追加されて、触れるようになっているはずだ。
コンポーネントの export
今の状態ではコンポーネントを外部から使うことができない。export
を利用して外部に公開できるよう、各フォルダのindex.ts
にexport
文を追加していく。
export * from "./Button" // Button.tsx ファイル
export * from "./Button" // Button フォルダ
export * from "./components"
これで、npm install
した側で以下のように記述すれば、Button
コンポーネントを利用できるようになる。
import { Button } from "<パッケージ名>";
これでライブラリ自体は用意できた。
デプロイの準備
パッケージとして公開するためには様々な設定が必要になる。まずはvite.config.ts
を以下のように設定する。
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
export default defineConfig({
plugins: [react()],
+ build: {
+ outDir: 'dist', // default の設定と同じ
+ lib: {
+ entry: 'src/index.ts',
+ name: '<パッケージ名>',
+ fileName: 'index',
+ formats: ['es', 'umd'], // default の設定と同じ
+ },
+ rollupOptions: {
+ external: ['react', 'react-dom'],
+ output: {
+ globals: {
+ react: 'React',
+ 'react-dom': 'ReactDOM',
+ },
+ },
+ },
+ },
});
build.rollupOptions
にある設定はVite 公式の説明にもあるようなライブラリにバンドルしたくない依存関係に関するものである。react
等はこのライブラリを使うときに入っている前提であるので、これらはバンドルしないように設定した方が良い。これに関して追加でpackage.json
に以下の記述を追加する。
{
...
+ "peerDependencies": {
+ "react": "18.2.0",
+ "react-dom": "18.2.0"
+ },
...
}
peerDependencies
とはこのライブラリをnpm install
する際に、すでにinstall
されているものとして取り扱う依存先を記述するためのものである。その挙動はnpm
のバージョンによって挙動が異なるので注意が必要である。
次にtsconfig.json
では以下のように付け足す。
{
"compilerOptions": {
...
/* Bundler mode */
...
+ "emitDeclarationOnly": true,
+ "declaration": true,
+ "declarationMap": true,
+ "declarationDir": "dist",
- "noEmit": true,
...
},
"include": ["src"],
+ "exclude": ["**/*.stories.*"],
...
}
まず、compilerOptions
のdeclaration
に関係する記述だが、TypeScript の型情報をバンドルするための設定である。初期設定のままだと型情報はバンドルされない。なぜならば、多くのプロジェクトはライブラリではなく Web サービスの構築であるので、その運用に型情報は必要ないからである。
次にexclude
で Storybook に関係するスクリプトをライブラリにバンドルしない設定である。これに加えて例えば**/*.mdx
や Vitest などのテストツールを使っているのであれば**/*.spec.*
や**/*.test.*
もライブラリ本体には必要ないのでこの項目に入れておくと良いだろう。
最後にpackage.json
のビルドのタスクランナーを少し調整する。
{
...
"type": "module",
...
"scripts": {
"dev": "storybook dev -p 6006",
+ "build": "vite build && tsc",
- "build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
},
...
}
tsc
コマンドとvite build
コマンドの順番を入れ替えるだけである。これはvite build
コマンドがビルドファイルの出力先であるdist
フォルダをクリーンにしてしまうので、tsc
で生成された型情報ファイル(これもtsconfig.json
のdeclarationDir
でdist
フォルダに入れるよう設定している)が消し飛んでしまうことを避けるためである。
一度これでnpm run build
を実行してみると、上記と同じ設定をしているのであれば以下のようなdist
フォルダが作成される。
$ tree dist
dist
├── components
│ ├── Button
│ │ ├── Button.d.ts
│ │ ├── Button.d.ts.map
│ │ ├── index.d.ts
│ │ └── index.d.ts.map
│ ├── index.d.ts
│ └── index.d.ts.map
├── index.d.ts
├── index.d.ts.map
├── index.js
└── index.umd.cjs
index.js
が ESModule でindex.umd.cjs
が UMD のファイルである。index.d.ts
は TypeScript のための型定義ファイルになっている。これらのファイルパスをpackage.json
に記述する。
{
...
"name": "<パッケージ名>",
- "private": true,
+ "description": "<説明>",
+ "author": {"name": "<製作者名>"},
+ "version": "0.1.0",
- "version": "0.0.0",
"type": "module",
+ "main": "./dist/index.umd.cjs",
+ "module": "./dist/index.js",
+ "types": "./dist/index.d.ts",
+ "exports": {
+ ".": {
+ "types": "./dist/index.d.ts",
+ "import": "./dist/index.js",
+ "require": "./dist/index.umd.cjs"
+ }
+ },
+ "files": [
+ "dist"
+ ]
...
}
"private": true
の項目が残っているとnpm publish
が実行できなくなるので注意が必要である。
デプロイおよびインストール・・・は別記事で
さて、デプロイの準備が整ったが、実際のデプロイとインストールのテストについてはどのサービスにデプロイするかによって異なる。
デプロイ先については npm レジストリに行うのが最も楽だがこれは public なレジストリ[1]である。private に公開したい場合は GitHub Packages などのレジストリを使うと良いだろう。
GitHub Packages と Azure Artifacts については別の記事として書いたので参考にしてもらいたい。
記事全体としての参考サイト
-
npm でも 有料ユーザーであれば private な公開が可能らしい(https://docs.npmjs.com/creating-and-publishing-private-packages) ↩︎
Discussion