NU Angels에 디자인 시스템 적용하기

Robin
넥스트유니콘 팀 블로그
11 min readFeb 6, 2024

안녕하세요! 저는 NU Angels 디자인 시스템을 만들고 있는 프론트엔드 개발자입니다.

이번 글에서는 NU Angels 서비스에서 기존 디자인 시스템을 버리고 새로운 디자인 시스템을 적용하기로 한 배경 설명과 현재까지 어떤 개발을 했으며 앞으로의 개발계획에 대해 공유하려고 합니다.

배경

디자이너가 원하는 UI ≠ 실제 개발된 UI

서비스 초기 개발단계에서 기존 넥스트유니콘 디자인 시스템이 적용된 상태였고, 짧은 스프린트 기간에 기능, 디자인 개선을 둘 다 하기에는 무리가 있었습니다. 스프린트가 진행되면서 개발팀에서는 기능 추가에 집중하고 있었고, 디자인 쪽에서는 UI 컴포넌트 개선 작업을 진행하고 있었습니다. 그 결과, 디자인 쪽에서 요구하고 있는 UI 이랑 실제로 개발된 UI가 불일치한 부분이 있었습니다. 예를 들면 Button에서 icon과 같이 나올 때 padding 값이 다르다거나 Input 컴포넌트가 border-radius, background-color, font 등이 달랐습니다. 디자인 QA 때마다 이와 관련된 이슈들이 계속 나왔고, 이 부분에 대한 개선이 필요했습니다.

디자인 수정 및 추가 과정에서 개발자 ↔ 디자이너 사이의 비효율적인 커뮤니케이션 발생

저희 팀에서는 UI 컴포넌트 디자인 수정 및 추가 시 다음과 같은 플로우가 진행됩니다.

버튼 Background color 변경 → 슬랙을 통해 개발자에게 알린다 → 개발자가 읽음 → 코드 수정 → 배포

음…..🤔 봤을 땐 분명 일반적인 커뮤니케이션 방식이라고 생각하실 수도 있습니다. 근데 제 생각은 달랐습니다. 위의 2~4번(왼쪽 과정부터 1번이라고 정의할게요.) 과정이 불필요하다고 느꼈습니다. 왜냐하면 값이 변경됐는데 저 과정을 수동으로 해야 한다는 게 비효율적이라고 생각했기 때문입니다. 그래서 제가 생각한 개선 방향을 다음과 같습니다.

버튼 Background color 변경 후 Github에 push → token 값 변경 여부 체크 및 수정하는 Github Actions Workflow 수행 → 배포

위 플로우가 완성된다면 디자이너, 개발자 둘 다 커뮤니케이션 리소스가 줄어들 것으로 생각했습니다.

외부 UI 라이브러리 의존으로 인한 문제 발생

외부 UI 라이브러리를 사용하면 해당 UI 컴포넌트를 따로 만들 필요가 없기 때문에 개발 속도는 확실히 빠를 수 있습니다. 하지만 스프린트를 진행하면서 저는 다음과 같은 문제점을 발견했습니다.

  1. 외부 라이브러리를 설치하면 서비스에서 사용하지 않는 기능까지 설치가
    되기 때문에 메모리 낭비가 발생합니다.
  2. 해당 컴포넌트에 대한 추가 요구사항이 오면 해당 라이브러리에 대해 해당 기능에 대한 조사를 해야하는데 이에 대한 리소스가 생각보다 많이 들었고, 지원하지 않는 경우가 발생했습니다.

위 두 가지 문제점 때문에 우리 서비스가 쓰는 기능만 있고, 확장성에 용이한 컴포넌트를 만듦으로써 서서히 외부 UI 라이브러리를 제거하고자 합니다.

진행 상황

1. 빌드 테스트 자동화

저희 디자인 시스템은 Github Actions를 사용해 push 후 token 파일 및 코드를 빌드 및 배포를 진행하는 workflow가 실행됩니다. workflow에 대한 코드는 다음과 같습니다.

# publish-package.yml
name: Publish Packageon:
push:
branches: [master]
jobs:
build:
name: "Build"
runs-on: ubuntu-latest
steps:
# 해당 프로젝트를 리눅스 환경에 checkout하고 실행
- name: Checkout
uses: actions/checkout@v3
- uses: actions/configure-pages@v2
# Node.js 사용
- name: Node.js
uses: actions/setup-node@v3
with:
node-version: 18
# 사용하고 있는 의존성을 캐싱
- uses: actions/cache@v2
with:
path: "**/node_modules"
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}

# 이전 cache가 없다면 의존성을 설치합니다.
- name: Install Dependencies
run: yarn install
# 피그마 토큰 스튜디오에서 받은 json 파일을 JS 파일로 변환
# emotionJS 기반으로 style component를 구현하고 있기 때문에 JS 파일이 필요합니다.
- name: Build style-dictionary
run: yarn build:sd
# storybook build
- name: Build storybook
run: |
yarn build-storybook
mv ./storybook-static ./storybook
# storybook 빌드 결과물을 storybook 디렉터리에 저장
- name: Upload pages artifact
uses: actions/upload-pages-artifact@v1
with:
path: "storybook/"
# 'packages' 폴더 안에 있는 코드들 빌드
- name: Build packages
run: yarn build
deploy:
needs: build
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
# Github Pages에 배포
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v1

현재는 deploy 단계에서는 Github Pages에 storybook 배포만 진행하고 있고, package를 publish 하는 step은 추가를 안해놨습니다. 이 부분은 Github 브랜치 정책과 패키지 배포 정책 등을 좀 더 고민해보고 추가하려고 합니다.

2. Code Splitting

패키지를 사용시 필요한 코드만 불러올 필요가 있습니다. 예를 들어, 아래와 같이 Title 이라는 텍스트만 렌더링하는 코드가 있습니다.

// App.tsx
const App = () => {
return (
<div>
<h1>Title</h1>
</div>
);
};
export default App;

위 코드를 빌드하면 번들 사이즈는 아래와 같이 나옵니다.

여기서 Code Splitting 적용 전인 디자인 시스템 패키지에 있는 color 하나를 import해서 사용해보겠습니다.

// App.tsx
// Code Splitting 적용 전 패키지입니다.
import { ShineBlue10 } from "@nextunicorn-inc/nu-angels-design-system";
const App = () => {
return (
<div>
<h1 style={{ color: ShineBlue10 }}>title</h1>
</div>
);
};
export default App;

위 코드를 빌드하면 번들 사이즈는 아래와 같이 나옵니다.

위 이미지처럼 Code Splitting이 적용되지 않은 상태에서 build시 아래의 이미지와 같이 심각한 메모리 낭비가 발생하게 됩니다. 그 이유는 build시 모든 코드를 하나의 index.js에 집어넣기 때문입니다. 그렇기 때문에 color 하나 import 해왔을에도 불구하고 index.js에 있는 모든 코드를 가져온 것입니다.

Code Splitting을 적용하기 위해 rollup.config.js 에서 build시 각 컴포넌트에게 별도의 index.js 파일이 빌드 파일 결과물로 나오도록 설정했습니다.

// rollup.config.js
import multiInput from "rollup-plugin-multi-input";
const extensions = [".js", ".jsx", ".ts", ".tsx"];export default {
// 여러 input 값을 설정함으로써 빌드시 code splitting을 적용하도록 했습니다.
input: [
"./packages/index.ts",
"./packages/**/index.tsx",
"./packages/**/variables.ts",
],
output: [
{
dir: "dist",
format: "esm", // es모듈 형태로 번들링
sourcemap: true,
},
],
plugins: [
// rollup-plugin-multi-input 플러그인을 사용해 여러 input를 설정할 수 있도록 했습니다.
multiInput.default({
relative: "packages/",
}),
],
};

Code Splitting 적용 후 build 결과, 다음과 같습니다.

위 이미지를 보면 color에 대한 코드만 불러오기 때문에 번들 사이즈를 감소시킬 수 있었습니다.

3. Storybook을 사용해 UI 컴포넌트 테스트 및 문서화 작업

Storybook을 적용시킴으로써 시각적인 컴포넌트 테스트를 했습니다. 또한 구현된 컴포넌트를 문서화해 Github Pages를 사용해 다른 팀원들에게 공유할 수 있도록 배포를 했습니다.

저희 디자인 시스템에 대해 궁금하시면 여기에 들어가시면 구현된 컴포넌트에 대한 정보를 더 자세히 알 수 있습니다.

앞으로의 계획

1. 지속적인 UI 컴포넌트 개발로 외부 UI 라이브러리 의존도 최소화

현재 NU Angels 프론트에서는 일부 UI 컴포넌트에서는 UI 라이브러리로 대체하고 있습니다. 분명 초기 개발단계에 대한 리소스는 줄어드는 장점이 있지만 유지보수 면에서는 좋지 않다고 느꼈습니다. 그렇기 때문에 저는 저희 서비스에 최적화된 UI 컴포넌트를 만들어서 외부 UI 라이브러리에 대한 의존도를 최소화할 것입니다.

2. 재사용성, 확장성이 좋은 컴포넌트로 설계하기

어떤 컴포넌트가 좋은 컴포넌트일까요? 명확한 정답은 없다고 생각합니다. 사람마다 중요하게 생각하는 우선순위는 다르니까요. 제가 생각하는 좋은 UI 컴포넌트는 다음과 같다고 생각합니다.

디자이너가 요구하는 UI 명세서를 기본 UI 형태 + 합성 컴포넌트 패턴

위와 같이 구현되면 디자인에서 정해진 속성대로만 구현해버리면 확장성이 좋지 않습니다. 이때, 합성 컴포넌트 패턴으로 구현해서 디자인에서 정해진 속성은 기본 UI를 지니고, props를 통해 외부 컴포넌트와 조합할 수 있는 구조로 가면 디자인에서 요구하는 엄격함을 유지함과 동시에 재사용성과 확장성을 극대화할 수 있습니다.

3. Storybook을 좀 더 잘 활용해보기

아직까지는 storybook을 단순 시각적인 컴포넌트 테스트 + Component Documentation 역할밖에 못하고 있습니다. (제가 storybook에 대한 학습을 부지런히 못했습니다. 😭) 개인적인 욕심으로는 storybook을 더 멋지게 사용해서 컴포넌트 테스트 및 디자인 시스템 참고 페이지를 좀 더 고도화하고 싶습니다.

결론

디자인 시스템 프로젝트는 사이드 프로젝트 개념으로 가져가고 있습니다. 따라서 계속 밀고 들어오는 스프린트와 핫픽스때문에 개발 속도가 상대적으로 더딥니다.

하지만 덕분에 컴포넌트 설계에 대해 깊은 생각하게 되고 매우 만족한 개발을 진행하고 있습니다. 만약 저희 팀과 같은 고민을 하시는 분들이 있다면 한 번 시작해보시는 것도 좋을 것 같습니다.

--

--