この記事はファンタアドベントカレンダー2023の5日目です。
現在参加中のプロジェクトでUIライブラリを作成してnpmで配信してみたい!という話になりました。
この記事では、その環境構築手順を共有します
なお本記事で掲載した一部コードを含む全体をGitHubレポジトリで公開しています。
想定読者
- React * Vite * Tailwind CSSを使ったUIライブラリを実装したい方
- 自作のコンポーネントをパッケージで配信してみたい方
技術スタック
本記事で使用する主な技術スタックは以下の通りです
- React
- Vite
- TypeScript
- Tailwind CSS
1. 初期構築
以下のコマンドでViteのreact-tsテンプレートを用いてプロジェクトを作成します
yarn create vite . --template react-ts
パッケージをインストールする
(yarn.lockが無いよ!とエラーが出るため空のファイルを作成しています)
touch yarn.lock
yarn
2. Tailwind CSSの導入
スタイリングはtailwindを使いたいのでパッケージをインストールします
yarn add -D tailwindcss postcss autoprefixer
以下のコマンドでプロジェクトにtailwindを導入します
yarn tailwindcss init -p
この時生成されるpostcss.config.jsの内容はvite.config.tsに移行可能なので移してしまいます
tailwind.config.jsを書き換えます。
この際にtw_
など任意のプレフィックスをつけることができます。
必要に応じて設定してください。
型がある方が補完効いたり、エディタでエラー表示してくれたり何かと安心なので .ts
に変えてしまいました。
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を読み込みます
詳しくは公式ドキュメントを参照してください
src/index.css
@tailwind base;
@tailwind components;
@tailwind utilities;
3. ビルド設定周りを修正
vite.config.ts
2でpostcss.config.jsを削除したのでこちらに記述します
css: {
postcss: {
plugins: [tailwindcss, autoprefixer]
}
}
あとはビルド周りも追加します
build: {
lib: {
entry: resolve(__dirname, "src/index.ts"),
name: formattedName,
formats: ["es", "umd"],
fileName: (format) => `${formattedName}.${format}.js`,
},
}
最終的に以下のようなコードになりました
/// <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
が読み込めずエラーになる場合は以下のコマンドで型情報を追加してあげると解決します
yarn add -D @types/node
tsconfig.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
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts", "package.json"]
}
7. package.jsonの修正
以下のエントリポイント周りの設定をpackage.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
にビルドコマンドを追加します
"scripts": {
// ...
// ↓追加
"build": "tsc && vite build",
// ...
},
コンポーネントの追加
src下にコンポーネントを作成します
コンポーネントは1つのディレクトリにまとめたかったため作成したcomponents
ディレクトリ下にまとめました
src/components/Button/Button.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
import { ComponentProps } from "react";
export type ButtonProps = Pick<ComponentProps<"button">, "children" | "onClick">;
src/components/Button/index.ts
export * from "./Button";
src下でCSSと作成したコンポーネントをexportします
src/index.ts
import "./index.css";
export * from "./components";
8. ビルド
以下のコマンドでパッケージのビルドを実行します
yarn build
dist
フォルダ下にファイルが生成されていれば成功です
9. パッケージ公開
npmへのログインが完了していなければ認証情報を入れておきましょう
yarn npm login
以下のコマンドでnpmへパッケージを公開できます
npm publish
10. 動作確認
作成したコンポーネントは以下のように使用することができます
import { Button } from "yhi-component-lib"
export const SamplePage = () => {
return (
<div>
<Button>ボタンA</Button>
<Button>ボタンB</Button>
<Button>ボタンC</Button>
</div>
)
}
この際、ビルド時に出力したCSSを読み込むのを忘れないようにしましょう(自分は忘れてスタイルが当たらず1日潰しました)
以下のように出力されたCSSをインポートする文を全体で読み込むスタイルに含めておくと良いと思います
@import "yhi-component-lib/dist/style.css"
おわりに
実際にはこの記事の内容に加えて以下のようなこともやっているので、この辺りもどこかで書けたらいいなと思います
- Storybookの導入
- Vitestでテスト実行
- GitHub Actions上でnpmのパッケージ公開自動化
- Hygenを導入してコンポーネント作成をある程度自動化
明日は筋肉系デザイナーの朝妻さんが担当してくれます!お楽しみに!!!!