안녕하세요. J4J입니다.
이번 포스팅은 tailwind로 react 컴포넌트 라이브러리 배포하는 방법에 대해 적어보는 시간을 가져보려고 합니다.
관련 글
npm 패키지 배포, 처음부터 자동화까지 한 번에 정리
npm 패키지 배포, 처음부터 자동화까지 한 번에 정리
안녕하세요. J4J입니다. 이번 포스팅은 npm 패키지 배포하는 방법에 대해 적어보는 시간을 가져보려고 합니다. NPM 패키지 배포 npm 패키지 배포라고 하는 것은 한 번 만들어진 javascript 기반의 라이
jforj.tistory.com
React Typescript 컴포넌트 라이브러리 배포 가이드, tsup + tsc 활용
React Typescript 컴포넌트 라이브러리 배포 가이드, tsup + tsc 활용
안녕하세요. J4J입니다. 이번 포스팅은 react typescript 컴포넌트 라이브러리 배포하는 방법에 대해 적어보는 시간을 가져보려고 합니다. 관련 글 [React] Vite 사용하기 [React] Vite 사용하기안녕하세요.
jforj.tistory.com
Tailwind className 관리 가이드, clsx + twMerge + cva 역할 정리
Tailwind className 관리 가이드, clsx + twMerge + cva 역할 정리
안녕하세요. J4J입니다. 이번 포스팅은 tailwind를 사용할 때 className 관리하는 방법에 대해 적어보는 시간을 가져보려고 합니다. Tailwind를 사용하면 겪는 className 설정 문제점 요즘 react 프로젝트를
jforj.tistory.com
Tailwind로 만드는 React 컴포넌트
react 컴포넌트를 개발하여 라이브러리의 형태로 제공하는 방식에는 다양한 스타일링이 사용될 수 있습니다.
기본적인 style 처리 방법, css-in-js 그리고 tailwind 등 현재 상황에서는 라이브러리를 이용하여 컴포넌트를 제공해야 할 때 많은 선택지가 제공됩니다.
각각의 방법에는 여러 가지 장/단점을 가지고 있기 때문에 가장 좋다고 얘기할 만한 방식은 없다고 생각합니다.
다만, 라이브러리를 관리하는 개발자와 라이브러리를 활용하는 개발자가 각각 어떤 기술 스택을 소유하고 있는지에 따라 tailwind를 활용한 컴포넌트 개발 방식이 좋은 경험을 제공할 수 있습니다.
tailwind를 사용하여 라이브러리를 구성하면 좋은 이점들은 다음과 같이 있을 겁니다.
- build를 수행할 때 스타일이 css로 분리되기에 라이브러리 bundle 파일의 크기가 작아짐
- 그래서 런타임 단계 때 오버헤드를 줄일 수 있음
- tailwind를 사용하는 애플리케이션에서 친화적으로 사용할 수 있음
- 등등...
아무래도 가장 크게 체감될 수 있는 부분은 bundle 파일의 크기가 작아지는 것이 아닐까 생각됩니다.
정의된 class의 값을 제외하고는 스타일 처리를 위한 값들이 bundle 파일에 담기지 않기 때문에 애플리케이션 환경에서 더 가벼운 형태의 라이브러리를 활용할 수 있게 됩니다.
또한 tailwind를 사용하고 있는 프로젝트에서는 친근한 방식으로 개발을 수행할 수 있게 됩니다.
특히, clsx / twMerge / cva를 통해 각 프로젝트에서 스타일의 변경을 자유롭게 할 수 있는 구조로 컴포넌트를 제공해 준다면 class의 값을 프로젝트 별로 재 정의하여 쉽게 적용할 수 있도록 도와줍니다.
물론 단점도 존재할 수 있습니다.
아무래도 다음과 같은 단점들이 있다고 얘기할 수 있을 겁니다.
- 상황에 따라 애플리케이션 프로젝트에서 tailwind의 사용을 필수로 해야 할 수도 있음
- 또는 css 파일도 함께 배포하여 각 프로젝트에서 적용해 줘야 함
배포할 프로젝트 구성
배포 프로젝트를 구성하기 전 tsup이 무엇인지에 대해 궁금하신 분들은 이곳을 확인해 주시면 됩니다.
프로젝트를 구성하는 여러 방식이 있겠지만, 이곳에서는 vite 템플릿을 이용하여 다음과 같이 구성해 보겠습니다.
[ 1. 프로젝트 생성 ]
$ npm create vite@latest { 프로젝트 명 } --template react-ts
[ 2. tsup 구성 ]
// terminal
$ npm install -D tsup
// tsup.config.ts
import { defineConfig } from "tsup";
export default defineConfig({
entry: ["src/index.tsx"], // build 대상 entry 파일 등록
format: ["esm", "cjs"], // esm, cjs 기반의 build 파일 생성
dts: true, // type 처리를 위한 dts 파일 생성
clean: true, // build 전 이전 build 결과물을 제거
external: ["react", "react-dom"], // bundle 결과물에 포함시키지 않을 모듈 (peer deps 제거)
outExtension({ format }) {
return { js: format === "esm" ? ".mjs" : ".cjs" }; // format 별 확장자 설정
}
});
// tsconfig.json
{
"compilerOptions": {
/* tsup을 이용한 dts 빌드 사용 시 설정 필요 */
"allowImportingTsExtensions": true,
"jsx": "react-jsx",
"module": "NodeNext",
"moduleResolution": "NodeNext"
},
}
[ 3. tailwind 구성 ]
// terminal
$ npm install -D tailwindcss @tailwindcss/cli @tailwindcss/postcss postcss // 필수 설치
$ npm install clsx tailwind-merge class-variance-authority // 필요에 따라 설치
// postcss.config.mts
export default {
plugins: {
"@tailwindcss/postcss": {},
}
}
// tailwind.config.ts
import type { Config } from 'tailwindcss';
const tailwindConfig:Config = {
content: ['./src/**/*.{ts,tsx,js,jsx}'],
theme: { extend: {} },
plugins: []
}
export default tailwindConfig;
// /src/globals.css
@import "tailwindcss";
[ 4. package.json 배포 설정 ]
배포를 위한 package.json은 다음과 같이 설정해 줄 수 있습니다.
핵심은 tsup을 이용한 build를 수행하며 tailwind minify 설정을 통해 배포할 css 파일을 함께 생성해 주는 것입니다.
그리고 export에 css 파일도 함께 추가하여 애플리케이션 프로젝트에서 사용할 수 있도록 설정해 줍니다.
// package.json
{
"name": "@jforj26/tailwind-npm-deploy", // 배포되는 패키지 명
"version": "1.0.0", // 배포되는 버전
"description": "react + typescript + tailwind + tsup을 이용한 npm deploy 테스트를 위한 패키지", // 배포되는 패키지 설명
"exports": {
".": {
"types": "./dist/index.d.ts", // 패키지의 type 파일
"import": "./dist/index.mjs", // 패키지를 import 할 때 사용되는 파일
"require": "./dist/index.cjs" // 패키지를 require 할 때 사용되는 파일
},
"./globals.css": "./dist/globals.css" // 패키지의 스타일이 적용되는 css 파일
},
"types": "./dist/index.d.ts", // 일부 환경을 위한 type 파일 설정
"main": "./dist/index.cjs", // 일부 환경을 위한 cjs 기반 require 설정
"module": "./dist/index.mjs", // 일부 환경을 위한 esm 기반 import 파일 설정
"files": ["dist"], // 배포되는 파일/폴더
"keywords": [ // 패키지가 노출될 수 있는 keyword 구성
"button",
"text-field"
],
"author": "jforj", // 저작권자 설정
"license": "ISC", // 라이센스 설정
"publishConfig": { // public 기반으로 package 접근 가능하도록 설정
"access": "public"
},
"scripts": { // tsc + tsup을 이용한 build script 구성
"typecheck": "tsc --noEmit",
"build": "npm run typecheck && tsup && npx @tailwindcss/cli -i ./src/globals.css -o ./dist/globals.css --minify"
},
"peerDependencies": { // react, react-dom은 peer deps 설정으로 변경
"react": ">=19",
"react-dom": ">=19"
}
}
[ 5. 배포 컴포넌트 구성 ]
tsup의 entry 설정이 src/index.tsx 파일로 구성 되었기 때문에 배포되는 컴포넌트들은 해당 파일에 모두 담겨 있어야 합니다.
barrel 기반의 구조로 다음과 같이 간단하게 적용해 볼 수 있습니다.
// /src/cn.ts
import clsx, { type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export const cn = (...classValues: ClassValue[]) => twMerge(clsx(classValues));
// /src/buttons/Button.tsx
import { cva } from "class-variance-authority";
import type { ButtonHTMLAttributes, ReactNode } from "react";
import { cn } from "../cn";
const buttonVariants = cva([], {
variants: {
color: {
primary: 'bg-[#0F172A] text-white',
secondary: 'bg-[#38BDF8] text-white'
}
}
})
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
children?: ReactNode;
color?: 'primary' | 'secondary'
}
export default function Button({ children, className, color = 'primary', ...props }: ButtonProps) {
return (
<button className={cn(buttonVariants({color}), className)} {...props} >
{children}
</button>
)
}
// /src/buttons/index.tsx
export { default as Button, type ButtonProps } from './Button';
// /src/text-fields/TextField.tsx
import { cva } from "class-variance-authority";
import type { InputHTMLAttributes } from "react";
import { cn } from "../cn";
const inputVariants = cva([], {
variants: {
size: {
small: 'w-[6rem] h-[1.5rem]',
large: 'w-[8rem] h-[2rem]',
}
}
})
export interface TextFieldProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'size'>{
size?: 'small' | 'large'
}
export default function TextField({ className, size = 'small', ...props }: TextFieldProps) {
return (
<input className={cn(inputVariants({size}), className)} type="text" {...props} />
)
}
// /src/text-fields/index.tsx
export { default as TextField, type TextFieldProps } from './TextField';
// /src/index.tsx
export * from './buttons/index.tsx';
export * from './text-fields/index.tsx';
NPM 패키지 배포
패키지 배포하는 방법은 이곳에 정리 해놨습니다.
배포하는 방법을 모르시는 분들은 참고 부탁드립니다.
npm에 배포가 이루어졌다면 다음과 같은 방식으로 각 프로젝트에서 설치할 수 있습니다.
$ npm install { 패키지 명 }
패키지를 설치한 뒤 다음과 같은 방식으로 컴포넌트를 사용해 볼 수 있습니다.
import '@jforj26/tailwind-npm-deploy/globals.css';
import { Button } from '@jforj26/react-npm-deploy';
export default function App() {
return (
<div>
<Button onClick={() => console.log('button')}>button!</Button>
</div>
);
}'SPA > React' 카테고리의 다른 글
| Tailwind className 관리 가이드, clsx + twMerge + cva 역할 정리 (0) | 2026.01.18 |
|---|---|
| React Typescript 컴포넌트 라이브러리 배포 가이드, tsup + tsc 활용 (0) | 2026.01.10 |
| [React] SSE (Server-Sent Events) 사용하여 실시간 통신하기 (0) | 2024.05.08 |
| [React] Jest에서 호출되는 함수 mocking 하기 (0) | 2024.01.20 |
| [React] Jest에서 path alias 적용하기 (0) | 2024.01.18 |
댓글