React UI Kit 만들기 대작전

React UI Kit 만들기 대작전

리액트 프로젝트를 새로 시작하기 앞서, 프로젝트 전반에 걸쳐서 사용되는 UI Kit을 만들기 위해서 두 가지 시도를 했던 내용에 대해서 적어보려고 한다. 한 가지는 Private Package이고, 다른 한 가지는 StoryBook이다.

프라이빗 패키지는 npm에 공개된 패키지가 아니라, 특정 권한이 있어야 설치가 가능한 패키지를 말한다. 프라이빗 패키지는 클라이언트, 백앤드 작업을 모두 TS로 하면서 가끔 공유하고 있는 타입이나 클래스를 일종의 패키지로 구성한 적이 있다. StoryBook은 클라이언트 개발자들에게 Swagger와 같은 역할을 해주는 컴포넌트 뷰어라고 볼 수 있다. 스토리북에 대해서는 이전부터 알고는 있었지만, 클라이언트 개발에 항상 반만 진심이었던 필자는, 사용할만한 상황이 오지 않았다. 이런 UI 킷을 만들어야 하는 경우가 생기면, UI가 어떻게 생겼는지 보면서 개발하기가 애매한 경우가 있는데, 이런 문제를 깔끔하게 해결해줬다.

스토리북 메인에 등장하는 소개 영상

Private Package를 관리하는 것 보다, Submodule을 만드는 것도 하나의 방법이 될 수 있을 것 같다.

Private UI Library Package 만들기

필자는 프라이빗 패키지를 Github에 배포하는 편이다. Github에서 패키지 관리를 할 수 있는 기능을 추가하면서 가능해졌는데, 이를 위해서는, 기본으로는 NPM 패키지로 배포가 되기 때문에, 배포할 때 패키지가 어디에 배포되는지 명시해야 한다. 물론 패키지를 가져올 때에도 그렇다.

실제로 가장 별로라고 생각했던 점 중에 하나이다.

그 역할을 해주는 것이 바로 .npmrc이다. 이름 그대로 npm 설정 파일이고, 이 파일 안에 배포하는 패키지가 Github package repository를 바라보게 하고, 그 패키지 권한이 있는 토큰을 함께 명시해주면 된다. 아래와 같은 모습을 갖게 된다.

1
2
//npm.pkg.github.com/:_authToken=[string]
@[package-scope]:registry=https://npm.pkg.github.com/

첫 번째 줄에는 npm.pkg.github.com에 접근할 때 권한을 어떤 권한으로 접근하게 되는지를 설정한다. authToken은 권한마다 다르게 발급된다. 권한을 받으려면, 깃헙의 Settings 페이지 아래 Developer settings 페이지에서 받을 수 있다.

두 번째 줄에는 배포시에 레포지토리가 속한 Organization? 이라고 할 수 있는 걸 정의 해둬야 한다. 예를 들어서 toy-program이라는 이름의 Organization에 배포한 경우, @toy-program을 붙여주면 된다.

이 작업이 끝나면, npm.pkg.github.com에 접근할 때는 [string] 권한을 가지고 접근하고, 이 프로젝트의 package.json에서 @[package-scope]에 포함되는 패키지들은 npm.pkg.github.com에서 찾도록 해라! 라고 설정을 마친 것이다.

package.json 설정하기

기본 설정

package.json에도 몇 가지 설정해줘야 할 부분이 있다. 어떤 파일을 대상으로 하는지, 어디 레포지토리에 올리는지, 배포 관련 설정 등이 있다. 아래 내용들을 추가해줬다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
...

"files": [
"lib"
],
"main": "lib/index.js",
"types": "lib/index.d.ts",
"repository": {
"type": "git",
"url": "[github repository url]"
},
"private": false,
"publishConfig": {
"directory": "lib",
"registry": "https://npm.pkg.github.com/"
}
// ...
}

위 설정을 마쳐야 Typescript로 만든 React 컴포넌트를 lib 디렉토리에 빌드 한 후 배포할 수 있었다. 배포 명령은 yarn publish로 할 수 있다.

주의할 점 몇 가지가 있다면, yarn publish 후, 명시된 버전이 배포된 다음엔 같은 버전으로 배포할 수가 없다. 버저닝에 신중을 가할 필요가 있다. 또한, 타입스크립트 프로젝트의 경우, 배포시 빌드가 완료된 부분을 배포하는데, 웹팩이나 tsconfig에서 Output으로 설정한 부분을 files에 추가해줘야 한다. 필자의 경우는 일반적으로 dist 이름이 붙는 부분을 lib으로 한 것이다.

husky, lint-staged 설정하기

huskylint-staged는 깃 훅을 쉽게 설정해둘 수 있도록 도와주는 패키지이다. 이전 글에서 husky를 간단하게 설명한 바 있지만, 아주 간략한 소개였다. 근데 사실 그 이상 디테일한 내용도 없다. 그냥 깃 훅을 적절하게 조합하면 되는 건데, lint-staged는 깃 훅이 동작할 때, 특정 파일에 대해 특정 커맨드를 실행할 수 있게 해준다.

1
2
3
4
5
6
7
8
9
10
11
12
{
...
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{js,ts,tsx}": "eslint --fix"
}
...
}

위와같이 package.json에 추가해서, 커밋 전에 린트를 돌려주고 수정 가능하면 수정하도록 설정해뒀다. 이제 협업을 할 때 정한 코드 컨벤션에 맞지 않는 코드가 커밋되는 것을 방어할 수 있다.

webpack.config.js 설정하기

“웹팩 설정이 뭐 크게 다르겠냐”고 생각했지만, 뼈 아픈 결과를 맛 보고, 꼭 기록해야지 하는 생각이 들게 만든 부분이 있었다. 바로 모듈과 관련된 내용이었다. 웹팩에는 라이브러리를 빌드할 때 빌드 타겟을 설정할 수 있는 옵션이 있다. libraryTarget이라고 하는 옵션인데, 웹팩 문서의 설명에는 라이브러리가 어떻게 exposed 될지 설정하는 값이며, 기본 값은 var이라고 한다.

웹팩이 빌드 할 때, ES 방식으로 import 할 수 있도록 target 설정

이제 빌드 하고 사용해봐야겠다고 생각하고, 빌드 후 yarn publish로 배포까지 했지만 리액트 프로젝트에서 사용이 안되는 문제가 있었다. 알아본 결과, 패키지를 빌드할 때, target을 설정해줘야 하는 이슈였다. 아마 그냥 빌드를 하면 ES Module 방식으로 import 할 수 없는 상태로 빌드되는 것 같았다.

1
2
3
4
5
  ...
output: {
...
libraryTarget: 'umd'
},

Webpack에 output 옵션 중 libraryTargetumd 옵션을 붙여주는 방식으로 해결했다. libraryTarget 옵션은 라이브러리가 어떻게 exposed 될지를 결정하는 옵션이다. 기본 옵션은 var이다.

옵션 값이 어떤 것을 의미하는지 웹팩 공식문서 내용을 통해 알아보려고 했지만, 무슨 의미인지 쉽게 알 수 없어 이 링크에서 내용을 학습했다.

umd옵션은 ES Module 방식, CommonJS 방식, AMD 방식 모두 모듈을 불러올 수 있도록 번들링 해준다. 위 모듈 임포트 방식에 대해서 정확히 알지 못 한다면, TOAST 개발 블로그 글을 꼭 참고하길 바란다. 정말 최고.

내용은 짧았지만, 이 문제를 경험하면서, 역시 나는 프론트를 제대로 공부하지 못했구나 싶은 생각도 들었다.

StoryBook 붙이기

What’s a Story

스토리는 렌더링 된 형태의 UI 컴포넌트를 뜻한다. 개발자는 여러 스토리들을 한 컴포넌트에 할당해 만들 수 있다. 컴포넌트가 여러 상태를 가질 수 있기 때문에, 지원 중인 모든 상황에 대해 스토리를 만들 수 있게 구성되어있다. 예를 들자면, 버튼의 Primary한 상태, Secondary 상태 등으로 나눌 수도 있고, 사이즈 별로 나눌 수도 있는 것이다.

인스톨

이미 컴포넌트 진행이 되어있는 프로젝트의 루트 디렉토리에서 설치할 수 있다.

1
npx sb init

매지컬리 아주 깔끔하게 스토리북이 설치된다.

스토리북에 버튼 한 번 붙여보기

예를 들어서 만들어진 RoundButton을 스토리북에 올려보자. 먼저 결과적으로는 이렇게 나오고 있다.

스토리북은 stories 디렉토리 안에 있는 모든 **.stories.tsx 형태를 컴포넌트로 올려주는 것 같다. stories/Buttons/RoundButton.stories.tsx라는 파일을 만들어서, 다음과 같이 코드를 짰다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React from 'react';
// also exported from '@storybook/react' if you can deal with breaking changes in 6.1
import {Story, Meta} from '@storybook/react/types-6-0';
import {RoundButton, RoundButtonProps} from '@/components/RoundButton';

export default {
title: 'Button/RoundButton',
component: RoundButton
} as Meta;

const Template: Story<RoundButtonProps> = (props) => <RoundButton {...props} />;

export const Default = Template.bind({});
Default.args = {
children: 'RoundButtonComponent'
};

이 글에서는 자세하게 설명되어 있지는 않지만, 일반적으로 Typescript 프로젝트를 구성할 때 절대경로를 만들어서 사용하는 편이다. @/* 형태의 절대경로는 루트 디렉토리 아래 packages라는 디렉토리로 경로를 찾게 해두었다.

이때 문제가 발생했는데, 절대 경로를 스토리북에서 제대로 컴파일 하지 못 하는 문제였다. 확인 결과, 스토리북에서 사용하는 webpack config는 따로 설정해줘야 한다는 점이다. 설정은 .storybook안에 두면 된다.

1
2
3
4
5
6
7
8
9
10
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const {TsconfigPathsPlugin} = require('tsconfig-paths-webpack-plugin');
const path = require('path');

module.exports = ({config, mode}) => {
...
config.resolve.plugins.push(new TsconfigPathsPlugin());
...
return config;
};

위 코드처럼, 스토리북이 웹팩 설정을 적용하기 전에, 주입을 넣는 것 방법으로 작동한다. TsconfigPathsPlugin를 사용해서 tsconfigs.json의 Path 설정을 공유하도록 만들었다.

별 다른 옵션을 안 넣어줬는데, Props에 따라서 컨트롤을 넣어준다. 저렇게 컨트롤러를 사용해볼 수도 있고, Docs처럼 컴포넌트를 정리해주기도 한다. 스크린 뷰를 휴대폰이나 테블릿 사이즈로 맞춰 볼 수도 있고 그리드를 넣어서 볼 수도 있다. 문서만 확인해봐도 다양한 기능들을 쉽게 쓸 수 있게 생기긴 했는데, 하나씩 시도해보고, 쓸만한 거 있으면 도입하면서 알아갈 필요가 있을 것 같다.

Reference

댓글

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×