naver/fe-news를 보던 중 2023년 한 해 동안 가장 많은 Github Star를 받은 JavaScript 프로젝트들에 대한 글을 발견했다.
제일 Star를 많이 받은 JavaScript 프로젝트는 무엇이엇을까? Bun? Excalidraw? StyleX? 모두 훌륭하지만, 정답은 shadcn/ui이다.
UI 도구가 Excalidraw와 Bun을 제쳤다는 게 굉장히 신기하지 않은가? 사실 shadcn/ui의 인기에는 몇 가지 특수성이 있었기 때문에 2024년의 2분기인 현재에도 여전히 사용해 보지 않았거나 친숙하지 않은 React 개발자들이 꽤 많을 거라 생각한다.
이 글은 shadcn/ui의 정체와 개념, 동작 방식을 탐구하는 글이다.
- shadcn/ui
- 어떻게 2023년 가장 많은 Star를 받았을까
- v0 UI Generator에서 shadcn/ui를 사용
- Tailwind CSS를 기반으로 한 UI 도구
- 독특한 컴포넌트 설치 방식
- 잘 만들어진 UI Component Collection
- 어떻게 사용하는가
- 1. initialize
- 2. add component
- 컴포넌트의 구성 방향
- shadcn/ui 작동을 위한 프로젝트 구성 요소
- components.json
- Tailwind CSS
- 원본 컴포넌트
- CLI script
- init
- add
- 고찰
- 참고
shadcn/ui
shadcn/ui는 Vercel의 shadcn이 만든 UI 도구로, Radix UI(Primitives)와 Tailwind CSS라는 상당히 최신 기술을 기반으로 하는 Component Collection이다. shadcn/ui가 많은 관심을 받은 이유 중 하나가 바로 스스로 "component library"를 거부하고 "component collection"이라 소개하기 때문이다.
This is NOT a component library. It's a collection of re-usable components that you can copy and paste into your apps.
- shadcn/ui (Introduction)
component collection? library? 단순한 말장난 같지만, 제작자의 의도를 명확하게 하자면 이렇다.
기존의 전통적인 컴포넌트 library들은 번들된 소스 코드를 패키지 매니저를 통해 프로젝트 의존성에 추가해 사용한다. 이와 달리 shadcn/ui는 번들되지 않은 컴포넌트 소스 코드를 프로젝트에 의존성으로 추가하지 않고, 말 그대로 복붙해서 컴포넌트 코드를 온전히 사용할 수 있다.
이런 독특한 방식으로 딱 사용자가 원하는 컴포넌트만 골라서 가져가면 되기 때문에 "library"가 아닌 "collection"이라 칭하는 것이다.
사용을 위해서는 npm 설치 없이 npx shadcn-ui <command>
형태의 npx 명령어만 사용하거나 직접 사이트에서 가져가면 된다. (npx 명령어를 실행될 때 컴포넌트 복붙과 의존성 추가가 자동으로 이루어진다)
컴포넌트 설치를 의존성으로 하지 않는다는 건 결국 컴포넌트 설치(복붙)이후의 코드와 번들링에 대한 관리 책임이 패키지 사용자에게로 넘어간다는 것인데, 이는 사용자와 제작자 모두에게 유의미한 장점을 제공한다.
- 패키지 사용자 입장: 책임을 넘겨받는다는 게 부정적으로 보일 수 있지만, 기존 외부 lib 컴포넌트를 사용할 때 존재하던 커스터마이징 제약, 불필요한 번들 크기 증가와 같은 pain point가 사라진다. 필요한 컴포넌트만 복붙하고 자유롭게 커스터마이징하며 사용하면 된다.
- 패키지 제작자 입장: 컴포넌트 커스터마이징 지원, 번들링 및 배포에 대한 부담이 사라진다. 커스텀 컴포넌트를 제작하고, 소스 코드를 어딘가에 올리고, 이를 다운로드할 수 있는 npx script만 제공하면 된다.
shadcn/ui의 컴포넌트들이 복잡도가 낮기 때문에 이 모든 게 가능하다.
외부 lib 컴포넌트에 대한 커스텀이 필요한데 지원이 잘 안될 때 CSS !important
를 사용해 오버라이드하거나, 프로젝트를 clone(or patch)해서 직접 수정하거나, 정 안되면 직접 비슷한 커스텀 컴포넌트를 만드는 경험은 프론트엔드 개발자라면 대부분 가지고 있을 것이다.
이를 디자인과 마크업을 제거해서 해결한 게 Headless UI로 오직 UI에 대한 로직만 library에 포함하고 있다.
shadcn/ui는 여기서 접근 방식을 달리해 컴포넌트 관리 책임을 사용자에게 넘기는 것으로 컴포넌트를 소스 코드 레벨에서 커스터마이징이 가능하게 만들었다. 외부 컴포넌트의 문제점을 컴포넌트 라이브러리를 잘 만드는 방식(Headless UI)이 아니라 설치 방식을 바꾸어 해결한 게 놀랍다.
shadcn/ui의 방향성은 외부에서 컴포넌트 기능을 제공하는 것이 아니라 온전히 컴포넌트 소유권을 넘겨주는 것이다.
어떻게 2023년 가장 많은 Star를 받았을까
그렇다면 어떻게 Excalidraw 같은 훌륭한 프로젝트를 제치고 가장 많은 Star를 받을 수 있었을까? 심지어 2024년 5월 현재까지도 Star 상승세가 엄청난데 여기에는 여러 가지 이유가 있다고 생각한다.
v0 UI Generator에서 shadcn/ui를 사용
Vercel이 제작한 v0 AI UI Generator는 프롬프트에 맞는 UI와 코드를 생성하는 도구로, shadcn/ui와 Tailwind CSS를 사용해 코드를 생성한다. AI가 생성하는 소스 코드에 사용된다는 사실만으로 흥미롭지 않은가?
Tailwind CSS를 기반으로 한 UI 도구
Tailwind CSS는 프론트엔드 생태계에서 뜨거운 감자 같은 녀석이였지만 결국 주류 기술로 자리를 잡았고, 이를 사용한다는 게 인기에 영향을 줬을 것이다. (Radix UI 또한 훌륭한 headless UI 라이브러리이다)
독특한 컴포넌트 설치 방식
기존에도 이런 방식으로 설치하던 프로젝트가 있었는지 모르곘지만, 필자의 길지 않은 개발자 인생에서는 shadcn/ui를 통해 처음 접했다.
이 방식은 프로젝트 기반의 Primitive Component, 유틸리티, hooks 같은 것들에 매우 효과적인 방식인 것 같고 재밌는 방식이다.
잘 만들어진 UI Component Collection
이전에 MUI, Ant Design, Chakra UI 같은 잘 만들어진 UI 컴포넌트 도구들이 인기가 있었던 것처럼, shadcn/ui 또한 기본 스타일은 약간 부족해 보일 수 있지만 Radix UI를 기반으로 잘 만든 UI 컴포넌트 도구이다.
어떻게 사용하는가
사용법은 공식 문서에 빌드 환경별로 잘 설명되어 있지만 간략하게 남기자면 아래와 같다.
1. initialize
npx shadcn-ui@latest init
명령어를 실행하면 아래 질문 중에 프로젝트 환경에 맞는 적절한 것들이 프롬프트로 표시된다. 각 질문에 맞는 값을 입력하면 설정이 완료된다.
1- Would you like to use TypeScript (recommended)? › no / yes2- Which style would you like to use? › Default3- Which color would you like to use as base color? › Slate4- Where is your global CSS file? › src/index.css5- Do you want to use CSS variables for colors? › no / yes6- Are you using a custom tailwind prefix eg. tw-?7- Where is your tailwind.config.js located? › tailwind.config.js Leave blank if not) ›8- Configure the import alias for components: › @/components9- Configure the import alias for utils: › @/lib/utils10- Are you using React Server Components? › no / yes
- 순서대로 아래와 같은 의미를 지닌다.
- TypeScript 사용 여부
- component style (default와 new-work이 존재함)
- 기반 color pallete
- tailwind import 등이 들어갈 global css file
- CSS variable 사용 여부
- 사용하는 tailwind prefix
- tailwind config 위치
- component가 저장될 path
- utils 파일이 저장될 path
- RSC 사용 여부
init 과정에서
tailwindcss-animate
,class-variance-authority
,clsx
같은 필수 library들이 자동으로 설치된다.
2. add component
설정이 완료되면 npx shadcn-ui@latest add
명령어로 원하는 컴포넌트를 설치하면 자동으로 컴포넌트가 init에서 설정한 컴포넌트 경로에 생성된다. 이제 자유롭게 설치된 컴포넌트를 사용하면 된다.
예를 들어 npx shadcn-ui@latest add button
을 실행하면 설치되는 컴포넌트의 모습은 아래와 같다.
1// src/components/ui/button.tsx2
3import * as React from "react"4import { Slot } from "@radix-ui/react-slot"5import { cva, type VariantProps } from "class-variance-authority"6
7import { cn } from "@/lib/utils"8
9const buttonVariants = cva(10 "inline-flex items-center ...",11 {12 variants: {13 variant: {14 default: "bg-primary text-primary-foreground hover:bg-primary/90",15 ...16 },17 size: {18 default: "h-10 px-4 py-2",19 ...20 },21 },22 defaultVariants: {23 variant: "default",24 size: "default",25 },26 }27)28
29export interface ButtonProps30 extends React.ButtonHTMLAttributes<HTMLButtonElement>,31 VariantProps<typeof buttonVariants> {32 asChild?: boolean33}34
35const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(36 ({ className, variant, size, asChild = false, ...props }, ref) => {37 const Comp = asChild ? Slot : "button"38 return (39 <Comp40 className={cn(buttonVariants({ variant, size, className }))}41 ref={ref}42 {...props}43 />44 )45 }46)47Button.displayName = "Button"48
49export { Button, buttonVariants }
@radix-ui/react-slot
는 add 명령어 실행 과정에서 자동으로 설치된다.
아래처럼 바로 꺼내 쓰면 된다.
1// Home.tsx2
3import { Button } from "@/components/ui/button";4 5export default function Home() {6 const [count, setCount] = useState(0);7
8 return (9 <div>10 <Button onClick={() => setCount(count + 1)}>Easy</Button>11 </div>12 );13}
Button 컴포넌트 기반을 닦는 과정을 npx 명령어로 해결했다. 이제 스타일만 디자인에 맞게 손보면 된다.
컴포넌트의 구성 방향
컴포넌트 구성은 스타일은 Tailwind CSS를 기반으로 하고, 기능은 Radix UI를 기반으로 한다고 단순하게 볼 수 있다.
컴포넌트 코드를 살펴보면 특이한 점을 한가지 발견할 수 있는데, 대부분 컴포넌트가 기능을 외부 React 라이브러리에 위임해 직접적인 구현을 최소로 하고 스타일링도 Tailwind CSS를 기반으로 기본적인 것만 처리한다.
이러한 구현 방식은 프로젝트 방향성을 생각하면 적절하면서도 어쩔 수 없는 부분이기도 하다. shadcn/ui 제작자의 직접적인 구현이 많아지게 되면 코드의 소유권을 사용자에게 넘기기 까다롭게 된다. 코드 인수인계하듯이 직접적인 구현 코드에 대한 설명이 shadcn/ui 레벨에서 필요하기 때문이다. 하지만 외부 React 라이브러리는 이미 그들이 잘 만들어둔 문서와 예제들이 많기 때문에, 설명할 필요 없이 사용자는 이를 직접 찾아보면 된다.
Don't reinvent the wheel
컴포넌트의 구현에 대한 자세한 내용은 아래 글에 잘 소개되어 있다. 자세한 내용을 알고 싶은 분들은 참조하면 좋을 것 같다.
The anatomy of shadcn/ui (https://manupa.dev/blog/anatomy-of-shadcn-ui)shadcn/ui 작동을 위한 프로젝트 구성 요소
shadcn/ui 프로젝트를 작동할 수 있게 해주는 요소는 다음 네 가지로 나눠볼 수 있다. (CLI 사용 기준)shadcn/ui의 내부 코드가 궁금하지 않다면, 이 부분은 넘어가고 바로 고찰로 이동을 추천한다.
- shadcn/ui config를 저장하는 components.json (Config)
- 컴포넌트가 기본적으로 사용하는 Tailwind CSS와 관련 라이브러리 및 유틸리티 함수 (Style)
- 원본 컴포넌트 코드 (Source)
init
,add
명령어를 위한 script (CLI)
shadcn/ui의 시스템은 위 네 가지 요소로 작동하며 일반적인 컴포넌트 라이브러리와 비교하면 요소가 많다고 볼 수 있다. 하지만 전체 구성의 크기는 작기 때문에 복잡도가 높지 않다. 이제 하나씩 살펴보자.
components.json
components.json
은 init
명령어에서 입력받은 프로젝트 설정(alias, 관련 파일들의 위치, rsc, tsx 사용 여부, 테마 등)을 담고 있다. CLI를 사용하려면 필요한 파일이다.
1{2 "$schema": "https://ui.shadcn.com/schema.json",3 "style": "default",4 "rsc": false,5 "tsx": true,6 "tailwind": {7 "config": "tailwind.config.js",8 "css": "src/index.css",9 "baseColor": "slate",10 "cssVariables": true,11 "prefix": ""12 },13 "aliases": {14 "components": "@/components",15 "utils": "@/lib/utils"16 }17}
위 설정의 style
, rsc
, tsx
, aliases
, tailwind
같은 프로젝트 설정에 맞춰서 shadcn/ui에서 컴포넌트를 가져올 때 적절하게 transform 해서 설치를 하게 된다.
e.g.
tsx: false
면 소스 코드를 ts -> js로 변환해 jsx 확장자 파일로 저장한다.
components.json
파일의 구성에 대한 자세한 설명은 공식 문서에서 확인할 수 있다.
Tailwind CSS
styling에 사용되는 Tailwind CSS도 컴포넌트 사용에 필수적이며 기본적인 config, css, 연관 라이브러리, utils는 init
과정에서 세팅되므로, 크게 직접 설정할 부분은 없다.
프로젝트에서 관장하는 Tailwind CSS 연관 파일은 총 3개이다. 각 파일 모두 shadcn/ui가 관리하는 템플릿이 존재하며, init
과정에서 자동으로 템플릿이 설치된다.
- tailwind config file
- global css file
cn
helper가 들어있는 utils file
1.
과 2.
는 Tailwind CSS 사용 시 필수적인 부분이므로 설명을 생략하고, 3.
에 대해 알아보자면 아래와 같은 cn
함수 하나 들어있는 유틸리티 파일이다.
1import { type ClassValue, clsx } from "clsx"2import { twMerge } from "tailwind-merge"3
4export function cn(...inputs: ClassValue[]) {5 return twMerge(clsx(inputs))6}
- 특별한 기능은 없고 조건부로 Tailwind CSS 클래스를 쉽게 추가하기 위해 사용한다.
clsx
는 객체, 배열 지원과 조건부 추가 등className
사용을 편리하게 해주며 동적으로className
string을 생성한다.
(e.g.['c', 'b', 0, true && 'disabled', { foo: true }]
->'c b disabled foo'
)tailwind-merge
는 Tailwind 유틸리티 클래스의 중복과 충돌 제거 등을 처리한다.
각 파일의 템플릿 소스는 아래와 같다.
tailwind config
1export const TAILWIND_CONFIG_TS = `2import type { Config } from "tailwindcss"3
4const config = {5 darkMode: ["class"],6 content: [7 ...8 ],9 prefix: "<%- prefix %>",10 theme: {11 container: {12 ...13 },14 extend: {15 keyframes: {16 ...17 },18 animation: {19 ...20 },21 },22 },23 plugins: [require("tailwindcss-animate")],24} satisfies Config25
26export default config`
tailwind utils
1export const UTILS = `import { type ClassValue, clsx } from "clsx"2import { twMerge } from "tailwind-merge"3
4export function cn(...inputs: ClassValue[]) {5 return twMerge(clsx(inputs))6}7`
tailwind global css
1const BASE_STYLES = `@tailwind base;2 @tailwind components;3 @tailwind utilities;4 `
v0.8.0 기준 템플릿이 설치될 때 아래처럼 그냥
fs.writeFile
을 때려버리기 때문에 기존 내용이 날아간다.
1await fs.writeFile(2 config.resolvedPaths.tailwindConfig,3 template(tailwindConfigTemplate)({4 extension,5 prefix: config.tailwind.prefix,6 }),7 "utf8"8)
원본 컴포넌트
원본 컴포넌트는 TypeScript와 빌드 결과물 JSON 두 가지 형태로 관리된다.
1. TypeScript 원본 소스
원본 컴포넌트들은 Radix UI를 기반으로 RSC, TypeScript, Tailwind CSS, Path Alias가 모두 적용되어 있으며, registry/<style>/ui/<name>.tsx
경로에 저장된다. 여기서 <style>
은 컴포넌트 스타일(default or new-york), <name>
은 컴포넌트 이름을 나타낸다.
스타일별로 컴포넌트 소스 코드(스타일링)가 다르기 때문에, 동일한 타입의 컴포넌트여도 분리되어 있다.
e.g. registry/default/ui/button.tsx
1import * as React from "react"2import { Slot } from "@radix-ui/react-slot"3import { cva, type VariantProps } from "class-variance-authority"4
5import { cn } from "@/lib/utils"6
7const buttonVariants = cva(8 "inline-flex ...",9 {10 variants: {11 ...12 },13 defaultVariants: {14 ...15 },16 }17)18
19export interface ButtonProps20 extends React.ButtonHTMLAttributes<HTMLButtonElement>,21 VariantProps<typeof buttonVariants> {22 asChild?: boolean23}24
25const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(26 ({ className, variant, size, asChild = false, ...props }, ref) => {27 ...28 }29)30Button.displayName = "Button"31
32export { Button, buttonVariants }
컴포넌트들을 프로젝트에 복붙하려면 컴포넌트 이름, 소스 코드, 필요한 의존성 등의 정보가 필요하다. 이를 효율적으로 관리하기 위해 별도 registry array를 만들고, 컴포넌트 이름과 의존성 내역, 컴포넌트 구성 파일들의 경로를 관리한다.
(배열이 파일 정적 분석을 통해 자동 생성되는 건 아니고 직접 작성하고 있다)
1import { Registry } from "@/registry/schema"2
3export const ui: Registry = [4 {5 name: "accordion",6 type: "components:ui",7 dependencies: ["@radix-ui/react-accordion"],8 files: ["ui/accordion.tsx"],9 },10 {11 name: "alert",12 type: "components:ui",13 files: ["ui/alert.tsx"],14 },15 {16 name: "alert-dialog",17 type: "components:ui",18 dependencies: ["@radix-ui/react-alert-dialog"],19 registryDependencies: ["button"],20 files: ["ui/alert-dialog.tsx"],21 },22 ...23]
name
: 컴포넌트 이름type
: registry type (컴포넌트는 모두components:ui
이다)dependencies
: 컴포넌트 의존성 라이브러리 배열registryDependencies
: 컴포넌트 의존성 registry 배열 (e.g.alert-dialog
의 경우button
을 사용하기 때문에button
이 포함되어 있다)files
: 컴포넌트에 필요한 파일들의 경로 (e.g. hooks가 추가로 필요하다면 해당 hooks 파일 경로도 files에 추가된다)
이후, 위 TypeScript 소스들을 기반으로 build 과정에서 실제로 CLI에 사용할 JSON 파일이 생성된다.
2. Transformed JSON
component 코드와 registry array는 JSON 파일들로 변환되며, CLI의 파일 복붙과 의존성 설치에 사용되게 된다.
component json은 아래처럼 registry 정보에 files
만 실제 소스 코드로 변환한 형태이다.
1{2 "name": "button",3 "dependencies": [4 "@radix-ui/react-slot"5 ],6 "files": [7 {8 "name": "button.tsx",9 "content": "import * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\" ... export { Button, buttonVariants }\n"10 }11 ],12 "type": "components:ui"13}
registry array는 원본과 동일하다.
registry/index.json1[2 {3 "name": "accordion",4 "dependencies": [5 "@radix-ui/react-accordion"6 ],7 "files": [8 "ui/accordion.tsx"9 ],10 "type": "components:ui"11 },12 {13 "name": "alert",14 "files": [15 "ui/alert.tsx"16 ],17 "type": "components:ui"18 },19 {20 "name": "alert-dialog",21 "dependencies": [22 "@radix-ui/react-alert-dialog"23 ],24 "registryDependencies": [25 "button"26 ],27 "files": [28 "ui/alert-dialog.tsx"29 ],30 "type": "components:ui"31 },32 ...33]
위 JSON 정보를 기반으로 컴포넌트를 추가할 때 registry array에서 필요한 컴포넌트 정보를 찾고, 해당 정보를 바탕으로 프로젝트 style에 맞춰서 component json을 가져와서 의존성과 컴포넌트 files를 설치하게 되는 것이다.
CLI script
CLI script는 project initialize, component add, component diff 총 세 가지 기능이 있다. init과 add 둘 다 shadcn/ui 문서에 수동으로 처리하는 방법이 나와 있기 때문에, 사실 꼭 사용할 필요는 없다. (Initialize > Manual, Component Installation > Manual)
diff는 experimental 상태로 업데이트가 있는지(로컬 컴포넌트와 코드가 다른지) 체크하는 기능이다. 사실 컴포넌트 직접 수정을 해버리면 의미가 없긴 하다.
init
init 과정은 config 세팅과 템플릿, 필수 의존성 설치로 이루어져 있다.
1. 프로젝트 설정을 입력받는다.
prompts 라이브러리를 사용해서 initialize의 질문들이 프롬프트로 표시된다.2. tailwind config, global css, utils를 템플릿으로 세팅해 준다.
각각의 템플릿은 #tailwind-css에서 확인할 수 있다.
3. 필수 의존성을 설치해 준다.
사용에 필요한 필수 의존성을 설치한다.
- tailwindcss-animate
- class-variance-authority
- clsx
- tailwind-merge
- project style이
default
면lucide-react
,new-work
이면@radix-ui/react-icons
을 설치한다.
위와 같이 과정이 매우 간단하며 실제 소스 코드는 cli/src/commands/init.ts에서 확인 가능하다.
add
add도 필요한 컴포넌트 정보를 가져와서 설치만 하면 되기 때문에 간단하다.
1. 입력받은 컴포넌트 정보를 registry array json에서 가져온다.
예를 들어 npx shadcn-ui@latest add alert-dialog
를 실행하면 다음처럼 registryDependency
인 button도 함께 정보를 가져온다.
1[2 {3 "name": "alert-dialog",4 "dependencies": [5 "@radix-ui/react-alert-dialog"6 ],7 "registryDependencies": [8 "button"9 ],10 "files": [11 "ui/alert-dialog.tsx"12 ],13 "type": "components:ui"14 },15 {16 "name": "button",17 "dependencies": [18 "@radix-ui/react-slot"19 ],20 "files": [21 "ui/button.tsx"22 ],23 "type": "components:ui"24 }25]
2. project style을 기반으로 컴포넌트 json을 가져온다.
registry array에서 추가할 컴포넌트 정보들을 찾은 뒤에는 project style에 맞춰 컴포넌트 json을 가져온다.
1// registry/styles/default/alert-dialog.json2{3 "name": "alert-dialog",4 "dependencies": [5 "@radix-ui/react-alert-dialog"6 ],7 "registryDependencies": [8 "button"9 ],10 "files": [11 {12 "name": "alert-dialog.tsx",13 "content": "import * as React from \"react\"\n...const Button = React.forwardRef...export { Button, buttonVariants }\n"14 }15 ],16 "type": "components:ui"17}18// registry/styles/default/button.json19{20 "name": "button",21 "dependencies": [22 "@radix-ui/react-slot"23 ],24 "files": [25 {26 "name": "button.tsx",27 "content": "\"use client\"\n\n...export {\n AlertDialog..."28 }29 ],30 "type": "components:ui"31}
3. files를 순회하며 파일 저장 및 의존성 설치를 한다.
각 파일의 내용을 프로젝트에 저장하고, 의존성을 설치한다.
1for (const file of files) {2 let filePath = path.resolve(targetDir, file.name)3
4 // Run transformers.5 const content = await transform({6 filename: file.name,7 raw: file.content,8 config,9 baseColor,10 })11
12 if (!config.tsx) {13 filePath = filePath.replace(/\.tsx$/, ".jsx")14 filePath = filePath.replace(/\.ts$/, ".js")15 }16
17 await fs.writeFile(filePath, content)18}
targetDir
은 사실상components.json
의aliases.components
경로를 나타낸다.transform
함수에서 config에 맞게 file content를 변환한다.
1const packageManager = await getPackageManager(cwd)2
3if (item.dependencies?.length) {4 await execa(5 packageManager,6 [7 packageManager === "npm" ? "install" : "add",8 ...item.dependencies,9 ],10 {11 cwd,12 }13 )14}15
16// Install devDependencies.17if (item.devDependencies?.length) {18 await execa(19 packageManager,20 [21 packageManager === "npm" ? "install" : "add",22 "-D",23 ...item.devDependencies,24 ],25 {26 cwd,27 }28 )29}
execa
는 command를 실행해 주는 lib이다.packageManager
를 가져와서 그에 맞는 명령어로dependencies
를 설치한다.
실제 소스는 cli/src/commands/add.ts에서 확인 가능하다.
고찰
shadcn/ui 프로젝트가 어떻게 시작된 건지는 알 수 없지만, 컴포넌트를 복붙해서 쓰는 컬렉션 형태로 React 컴포넌트 프로젝트를 생각했다는 게 대단하다. shadcn/ui를 알아보면서 이런 형태의 프로젝트 컨셉이 기존에 가지고 있던 생각의 프레임을 많이 벗어나 있어서 생각의 성장에 도움이 되었다.
대부분의 현업 프로젝트는 내부적인 디자인 시스템과 컴포넌트가 존재하는데 그런 환경의 기반을 닦는데 shadcn/ui가 매우 최적화된 프로젝트라 생각한다. 새로운 웹 프로젝트를 만드는데 디자인이 존재한다면 shadcn/ui로 시작하지 않을까? 물론 단순 internal이거나 디자인 맞출 필요가 크게 없는 프로젝트면 장점이 많이 사라져서 다른 기술들과 차용을 고민해 볼 것 같다.