처음 UI 라이브러리를 만들기로 했을 때 타 라이브러리들을 열심히 참고하여 장점만 쏙쏙 빼와야지 생각했었다.
대부분의 라이브러리들이 모든 컴포넌트를 강제로 설치하도록 하는 것과는 달리, 몇몇 라이브러리들은 필요한 컴포넌트만 개별 설치할 수 있도록 한다.
이렇게 하면, 사용자가 사용하지 않을 컴포넌트가 깔리지 않아 훨씬 가볍게 서비스를 만들 수 있겠다는 생각을 했다.
이와 같은 방식을 제공하는 라이브러리로는 Radix UI
, Next UI
, Shadcn UI
가 있고, 어떻게 개별 컴포넌트를 다운받을 수 있도록 했는지를 알아보자.
Radix UI와 Next UI에서 사용하는 방법은 간단하다.
각 컴포넌트마다 npm에 배포하면 된다.
무슨 이야기인지 자세히 알아보자.
npm에 Next UI와 Radix UI를 검색해보자.
각각 81개, 259개가 뜬다.
이렇게 많은 패키지가 뜨는 이유는, 각 컴포넌트마다 npm에 올라가있기 때문이다.
즉, radix ui의 accordian, button 등이 각각 따로 npm에 올라가 있기 때문에 npm install @radix-ui/react-accordion
이 명령어를 통해서 accordian만 설치할 수 있다.
이를 구현하려면 어떻게 해야할까?
해당 라이브러리 깃허브를 탐색해보며 알아보자!
Radix UI나 Next UI에서 각 컴포넌트가 존재하는 위치로 가보면, 꼭 해당 폴더에 package.json
이 존재한다.
package.json에 의존성 정보나 패키지 이름, 버전 등을 명시해주어야 npm에 패키지를 배포할 수 있다.
즉, 각 컴포넌트마다 package.json을 만들어주고, 이를 기반으로 npm 배포를 진행하였음을 알 수 있다.
Shadcn UI는 위 두 라이브러리와는 달리, npm에 검색하면 컴포넌트 별로 다운받을 수 있는 패키지가 없다.
하지만, 공식 문서에 가면 shadcn ui의 특정 컴포넌트만 다운받을 수 있는 명령어가 존재한다.
과연 Radix UI 방식과 어떤 차이가 있는 걸까?
This is NOT a component library. It's a collection of re-usable components that you can copy and paste into your apps.
What do you mean by not a component library?
I mean you do not install it as a dependency. It is not available or distributed via npm.
Pick the components you need. Copy and paste the code into your project and customize to your needs. The code is yours.
Use this as a reference to build your own component libraries.
Shadcn UI 공식 문서를 살펴보면, Shadcn UI는 설치가 아닌 복사/붙여넣기를 사용한다고 한다.
install을 거치지 않아 의존성을 줄이면서도, 유저에게 코드에 대한 모든 권한을 넘겨 자율성을 높이기 위해 이와 같은 방법을 사용했다고 한다.
Shadcn UI 깃허브 레포지토리에서 컴포넌트 파일의 위치를 확인해보면,
좀 전과 달리 해당 폴더에 package.json 파일도 없고, 모든 컴포넌트들이 모여져 있다.
이번에는 공식문서에서 제공하는 명령어를 비교해보자.
// shadcn 명령어
npx shadcn-ui@latest add accordion
// radix 명령어
npm install @radix-ui/react-accordion
똑같은 아코디언 컴포넌트를 설치하기 위함인데 Shadcn UI와 Radix UI가 다르다.
Radix UI는 특정 패키지를 설치하기 위한 다른 명령어들과 다를 게 없지만, Shadcn UI의 명령어는 무언가 익숙하지 않다.
지금까지의 내용으로, Shadcn UI 명령어는 설치가 아닌 코드 복제를 해주는 명령어임을 알 수 있다.
Shadcn UI의 깃허브를 뜯어보며, 어떻게 코드 복제가 이루어지는지 분석해보자!
일단, add
라는 cli 명령어를 만드는 코드를 찾아보자.
처음에는 모르는 라이브러리 투성이라 코드 해석하는 데에 애를 먹었지만, 요약하자면 다음과 같다.
Commander.js
는 cli 명령어를 만들어주는 라이브러리이다.
해당 라이브러리를 사용하면, 특정 명령어를 호출하여 원하는 기능(함수)를 연결할 수 있다.
위 코드에서는 init 함수, add 함수, diff 함수를 cli 명령어로 생성하였다.
init 함수
init 함수는 프로젝트에서 Shadcn UI를 사용하는 데에 필요한 세팅들을 담당한다.
예를 들어, 원하는 컴포넌트 파일의 위치나, tailwind에 관련된 설정 등을 담당한다.
add 함수
복사를 통한 컴포넌트 추가를 담당한다.
diff 함수
registry에 올라가있는 컴포넌트와 현재 사용자가 가지고 있는 컴포넌트를 비교하여 업데이트를 알려주는 역할을 수행한다.
아직까지는 실험적인 기능인 듯 하여, 이 정도만 파악해보았다.
Shadcn UI의 add 함수가 어떻게 동작하는지 자세히 분석해보았다.
prompt
라는 라이브러리를 사용한다.const { components } = await prompts({
type: "multiselect",
name: "components",
message: "Which components would you like to add?",
hint: "Space to select. A to toggle all. Enter to submit.",
instructions: false,
choices: registryIndex.map((entry) => ({
title: entry.name,
value: entry.name,
selected: options.all
? true
: options.components?.includes(entry.name),
})),
})
해당 라이브러리에서는 입력을 텍스트로 받는 것뿐만 아니라, select를 통해 고르게 하는 등의 옵션을 사용할 수 있다.정보 가져오기
Shadcn UI에서는 컴포넌트에 대한 정보를 웹에 올려 놓았다.
이를 통해 별도의 설치 없이도 각 컴포넌트를 사용하기 위해선 어떤 의존성이 설치되어야 하는지, 컴포넌트 파일의 content는 어떻게 생겼는지 등의 정보를 알 수 있다.
add 함수 내부에는 해당 링크에 접근하여 컴포넌트에 해당하는 정보를 가져오는 코드가 포함되어 있다.
// 웹으로부터 컴포넌트 정보나 디자인 관련 설정을 받아오는 코드
const tree = await resolveTree(registryIndex, selectedComponents)
const payload = await fetchTree(config.style, tree)
const baseColor = await getRegistryBaseColor(config.tailwind.baseColor)
의존성 설치
Shadcn UI 코드를 분석하면서 정말 꼼꼼하다고 느꼈던 것은, 유저가 사용하는 패키지 매니저를 파악하여 이에 맞춰 의존성이 설치가 되고, 의존성을 중복해서 설치하지 않도록 설치하기 전 중복된 의존성을 제거하는 코드가 잘 작성되어 있다.
컴포넌트 복사
2번으로부터 받아온 정보를 통해 컴포넌트 파일의 내용이 string으로 변환된 형태를 얻을 수 있다.
이제 node js의 file system을 사용하면 해당 string을 tsx 파일로 작성할 수 있다.
// filePath에 파일이 작성되길 원하는 위치와 확장자를 포함한 파일의 이름를,
// content에 파일의 내용을 작성하면
// 해당 위치에 파일이 생성된다.
fs.writeFile(filePath, content)
이 코드들과 동작 원리를 이해하는 데에 약 2주가 걸렸다.
사실, Shadcn UI에서 CLI 관련 코드들을 살펴보면 정말 감탄이 절로 나오는 구현과 예외처리 등이 많은데 전반적인 흐름에 대한 정보를 전달하기 위해 이번 포스팅에서는 생략했다.
현재 실제 실습을 통해 Shadcn UI의 실습을 연습해고 있어서 아마 이후 포스팅에서 그런 놀라운 구현에 대해 다루게 될 것 같다.
이 2주간의 여정을 통해서 들었던 생각은
Shadcn UI 내가 만들었어야 했는데...!