levi

리바이's Tech Blog

Tech BlogPortfolioBoard
AllActivitiesJavascriptTypeScriptNetworkNext.jsReactWoowacourseAlgorithm
COPYRIGHT ⓒ eunwoo-levi
eunwoo1341@gmail.com

📚 목차

    [React] AST 기반으로 디자인시스템 UI 사용 현황을 정량화한 이야기

    ByEunwoo
    2026년 5월 1일
    react

    "디자인 시스템은 있는데, 왜 계속 깨질까?"

    우아한테크코스를 하던 시절, 우아콘 2025에서 당연해진 디자인 시스템, 그다음 이야기: AST와 MCP로 여는 미래 세션을 들으면서 하나의 장면이 계속 머리에 남았다.

    코드를 분석해서 하드코딩된 스타일을 찾아내고,
    그걸 토큰으로 바꿔나가는 흐름이었다.

    그걸 보면서 들었던 생각은 단순했다.

    "우리는 디자인 시스템이 없어서 문제일까,
    아니면 있는데 안 쓰여서 문제일까?"

    Moment 프로젝트에는 이미 디자인 시스템이 있었다.
    공통으로 사용할 수 있는 UI 컴포넌트들이 shared/design-system 영역에 분리되어 있었고, 버튼 같은 컴포넌트도 다음처럼 가져다 쓸 수 있었다.

    import { Button } from '@/src/shared/design-system';

    즉, 문제는 “디자인 시스템이 아예 없다”가 아니었다.
    오히려 더 애매한 상태였다.

    디자인 시스템은 있는데, 실제 기능 개발 과정에서는 화면마다 조금씩 다른 방식으로 UI가 만들어지고 있었다. 어떤 화면은 공통 컴포넌트를 잘 사용했지만, 어떤 화면은 비슷한 UI를 별도로 구현했다. 어떤 곳은 토큰을 사용했지만, 어떤 곳은 스타일 값을 직접 작성했다.

    이런 문제는 처음부터 크게 보이지 않는다.
    작은 화면 하나를 빠르게 만들 때는 오히려 직접 스타일을 작성하는 편이 더 빠르게 느껴진다. 공통 컴포넌트의 variant를 추가하거나 토큰을 확인하는 것보다, 지금 필요한 값을 바로 넣는 게 당장의 개발 속도는 빠르기 때문이다.

    하지만 이런 결정이 여러 번 반복되면 어느 순간 코드베이스에는 비슷하지만 조금씩 다른 UI가 쌓인다.
    그리고 그때부터 디자인 시스템은 “존재하지만 충분히 사용되지 않는 도구”가 된다.

    코드 리뷰를 하면서도 비슷한 이야기가 반복됐다.

    "이건 공통 컴포넌트로 뺄 수 있지 않을까?"
    "여기는 토큰을 쓰는 게 좋지 않을까?""
    "이 UI는 기존 컴포넌트랑 거의 비슷한 것 같은데?"

    하지만 이런 의견은 대부분 감에 가까웠다.
    실제로 공통 컴포넌트가 얼마나 쓰이고 있는지, 토큰 사용률이 어느 정도인지, 하드코딩된 스타일이 어느 파일에 집중되어 있는지는 알 수 없었다.

    결국 우리가 답해야 했던 질문은 이것이었다.

    디자인 시스템이 이미 있는 상태에서,
    실제 제품 코드가 그 시스템을 얼마나 따르고 있을까?

    이 질문에 답하지 못하면 개선 방향도 흐려진다.
    컴포넌트를 더 만들어야 하는지, 기존 컴포넌트 API를 고쳐야 하는지, 토큰 체계를 정리해야 하는지, 특정 화면부터 리팩토링해야 하는지 판단할 근거가 없기 때문이다.

    그래서 처음부터 디자인 시스템을 다시 만들기보다, 먼저 현재 상태를 측정해보기로 했다.

    목표는 단순했다.

    감으로 알고 있던 문제를, 코드 기반 데이터로 확인해보자!

    2. 진짜 문제는 "모른다는 것"이었다

    코드를 보면서 대충 이런 감은 있었다.

    • "하드코딩이 좀 있는 것 같다"
    • "공통 컴포넌트가 덜 쓰이는 것 같다”
    • "디자인이 조금씩 깨진다"

    근데 막상 질문을 구체적으로 던지면 아무도 답을 못 했다.

    • 공통 Button은 실제로 얼마나 쓰이고 있는가?
    • 토큰은 코드에서 몇 퍼센트나 쓰이고 있는가?
    • 하드코딩된 스타일은 어디에 집중되어 있는가?
    • 어떤 영역부터 개선해야 하는가?

    결국 모든 논의가 이렇게 끝났다.

    “일단 전체적으로 정리해야 할 것 같아요”

    이건 해결 방법이 아니다. 그냥 막막함이다.

    그래서 방향을 바꿨다.

    "디자인 시스템을 개선하자"가 아닌 "먼저, 현재 상태를 측정하자"

    3. 단순 검색으로는 해결이 안 되는 이유

    처음엔 가장 쉬운 방법부터 시도했다.

    파일 안에서 특정 문자열을 검색하는 명령어인 grep을 사용해서 Button이 어디서 쓰이고 있는지 찾아봤다.
    여기서 -r은 recursive, 즉 하위 폴더까지 전부 검색한다는 뜻이다.

    grep -r "Button" src

    하지만 바로 한계를 느꼈다.

    import { Button } from '@/src/shared/design-system';
    import { Button } from '@/src/features/post/ui';

    두 코드 모두 Button이지만 의미는 완전히 다르다.

    첫 번째는 디자인 시스템 컴포넌트
    두 번째는 특정 기능에서 만든 컴포넌트

    grep은 빠르지만, Button이 디자인 시스템에서 온 건지, feature 내부 컴포넌트인지, 그냥 문자열인지 구분을 못하기 때문에 정확한 분석 도구로는 부족하다.

    결국엔 내가 알고 싶었던 건 "디자인 시스템 컴포넌트가 실제로 얼마나 쓰이고 있는가?" 였기 때문에,
    코드 전체를 "문자열"이 아니라, "구조"로 분석할 필요가 있었다.

    4. AST로 코드 속 구조를 보기

    이 문제를 해결하기 위해 AST를 사용했다.

    **AST(Abstract Syntax Tree)**는
    코드를 텍스트가 아니라 트리 구조의 데이터로 표현한 것이다.

    예를 들어 이 코드가 있다고 하면,

    import { Button } from '@/src/shared/design-system';

    AST로 변환하면 이렇게 된다.

    {
      type: "ImportDeclaration",
      source: { value: "@/src/shared/design-system" },
      specifiers: [
        {
          type: "ImportSpecifier",
          imported: { name: "Button" }
        }
      ]
    }

    이걸 보면 알 수 있다.

    • 이건 import 문이다
    • 어떤 경로에서 가져왔는지 알 수 있다
    • 어떤 컴포넌트를 가져왔는지 알 수 있다

    즉, AST를 사용하면 이런 질문이 가능해진다.

    • 이 컴포넌트는 디자인 시스템에서 온 것인가?
    • 어떤 파일에서 어떤 컴포넌트를 쓰고 있는가?
    • import만 했는지, 실제로 JSX에서 사용했는가?

    이게 문자열 검색과의 가장 큰 차이였다.

    5. 구현 — Babel AST로 분석하기

    React + TypeScript 코드를 분석하기 위해
    @babel/parser와 @babel/traverse를 사용했다.

    @babel/parser는 코드를 AST로 변환해주는 도구이다.
    이를 통해 아래 코드와 같이 코드를 AST로 변환할 수 있었다.

    import { parse } from '@babel/parser';
    import traverse from '@babel/traverse';
     
    const ast = parse(code, {
      sourceType: 'module',
      plugins: ['typescript', 'jsx'],
    });

    @babel/traverse는 AST를 탐색할 수 있게 해주는 도구다.
    AST는 트리라서 직접 다 순회하기 귀찮은데,
    그래서 traverse가 "visitor 패턴"처럼 특정 타입의 노드에 도착했을 때 내가 적은 함수를 실행해주는 방식으로 작동한다.

    위에서 AST로 변환한 데이터를 순회하면서 필요한 정보를 수집할 수 있었다.

    traverse(ast, {
      ImportDeclaration(path) {
        const source = path.node.source.value;
     
        if (source.includes('/shared/design-system')) {
          // 디자인 시스템 컴포넌트 import 수집
        }
      },
    });

    위 코드를 예시로 보면, AST를 돌다가 ImportDeclaration 노드를 만나면 이 함수를 실행해줘라는 뜻이다.
    즉 import 문만 골라서 처리할 수 있다.

    5-2. 실제 JSX 사용 여부 확인

    traverse(ast, {
      JSXOpeningElement(path) {
        const name = path.node.name;
     
        if (name.type === 'JSXIdentifier') {
          const componentName = name.name;
          // 실제 사용된 컴포넌트 추적
        }
      },
    });

    import만 하고 안 쓰는 경우도 있기 때문에
    실제 JSX 사용까지 연결해서 집계했다.

    5-3. 하드코딩 스타일 값 탐지

    하드코딩 값은 AST로 위치만 찾고, 값은 정규식으로 처리했다.

    const HEX_REGEX = /#[0-9a-fA-F]{3,6}/g;
    const PX_REGEX = /\d+px/g;

    이 도구의 핵심은 이 한 줄로 정리된다.

    구조는 AST로 분석하고, 값은 정규식으로 탐지한다.

    6. 분석 결과

    Moment 코드베이스 327개 파일을 분석했다.

    항목수치
    분석 파일 수327개
    하드코딩 스타일 값895개
    토큰 사용 횟수434회
    토큰 채택률32.7%
    공통 컴포넌트20종 / 97회
    상위 10개 파일 집중도44%
    features 영역 비중41%
    semantic 토큰 사용률1%

    7. 숫자로 보니까 처음 보이는 것들

    처음에는 단순히 "하드코딩이 많다" 정도를 확인할 거라고 생각했다.
    근데 숫자를 보고 나서 생각이 완전히 바뀌었다.

    하드코딩은 "전체 문제"가 아니었다

    895개라는 숫자만 보면 막막하다.
    근데 분포를 보니까 달랐다.

    상위 10개 파일에서 44%가 발생

    이걸 보는 순간 생각이 바뀌었다.

    "전체 고칠 필요 없네"

    문제는 공통 컴포넌트가 아니라 features였다

    하드코딩의 41%가 features 영역에 있었다.

    이건 꽤 중요한 신호였다.

    공통 UI 자체보다,
    기능 구현 과정에서 디자인이 깨지고 있다는 의미였다.

    semantic 토큰은 사실상 쓰이지 않고 있었다

    토큰 사용률은 32.7%였지만
    그 안을 보면 대부분이 colors였다.

    semantic 토큰은 1%.

    이건 단순한 수치가 아니라 이런 의미였다.

    "토큰은 쓰고 있지만, 의미 기반으로는 쓰지 않는다"

    팀원들도 분석한 결과를 쉽게 보기 위해 Recharts라이브러리 기반 대시보드를 개발하여 하드코딩된 스타일과 토큰 사용 현황을 시각화했다.

    8. Codemod로 코드 자동 변환하기

    앞에서 AST 분석으로 디자인 시스템 사용 현황을 정량화했다.

    • 하드코딩 스타일 895개
    • 토큰 채택률 32.7%
    • 특정 파일에 집중된 구조

    여기까지가 "문제를 정확히 본 상태"라면,
    다음 단계는 자연스럽게 이어진다.

    "이걸 어떻게 줄일 수 있을까?"

    왜 수동 리팩토링은 한계가 있었을까?

    처음에는 단순하게 생각했다.

    "하드코딩된 부분을 하나씩 토큰으로 바꾸면 되지 않을까?"

    하지만 바로 현실적인 문제가 보였다.

    • 파일 수: 300개 이상
    • 하드코딩: 800개 이상
    • 위치: 여러 feature, 여러 스타일 파일

    이걸 사람이 하나씩 바꾸는 건 시간 오래 걸리고, 실수가 발생할 수 있다. 또한 기준 일관성 유지도 어려워진다.

    그리고 더 중요한 문제는 같은 색상인데 다르게 바꿔질 수 있다는 점이었다.
    예를 들어,
    #1E293B → slate-800, #1e293b → 다른 토큰, 일부는 그대로 유지.. 처럼

    이런 식으로 오히려 더 혼란스러워질 수 있다.
    그래서 수동 리팩토링 대신 자동 변환인 Codemod 방식을 선택했다.

    Codemod란?

    Codemod는 하드코딩된 색상 값을 디자인 토큰으로 변환하는 코드 변환 도구이다.

    이 프로젝트의 Codemod는 단순한 문자열 치환이 아니다.

    예를 들어,

    // Before
    export const Card = styled.div`
      background: #1e293b;
      color: #ffffff;
    `;

    이를 Codemod 적용 후에는

    // After
    export const Card = styled.div`
      background: ${({ theme }) => theme.colors['slate-800']};
      color: ${({ theme }) => theme.colors.white};
    `;

    즉, 아래처럼 모두 자동으로 처리한다.

    • 하드코딩 색상 → 토큰 표현식
    • 스타일 코드 → 디자인 시스템 구조로 정렬

    어떻게 동작할까?

    Codemod도 AST 기반으로 동작한다.
    전체 흐름은 이렇게 보면 된다.

    -> AST 파싱
    -> 색상 위치 탐색
    -> 토큰 매핑
    -> 코드 수정

    1. 토큰 → 색상 "역방향 맵" 생성

    일반적으로 토큰은 이렇게 정의되어 있다.

    slate-800: '#1E293B'
    white: '#FFFFFF'

    Codemod에서는 반대로 본다.

    '#1e293b' → 'slate-800'
    '#ffffff' → 'white'

    즉,

    색상 값을 보면 어떤 토큰인지 바로 찾을 수 있는 구조를 생성한다.

    2. AST로 교체 위치 찾기

    다음 두 가지 케이스를 탐지한다.

    (1) styled / css 내부

    styled.div`
      background: #1e293b;
    `;

    (2) JSX inline style

    <Heart color='#ef4444' />

    이 위치를 AST로 정확하게 찾는다.

    3. AST 전체 변환 대신 "부분 수정" 선택

    여기서 중요한 설계 결정이 있었다.

    • AST → 코드 전체 재생성 ❌
    • 필요한 부분만 문자열 수정

    이유:

    • prettier 포맷 깨짐 방지
    • 불필요한 diff 최소화
    • 코드 리뷰 부담 감소

    그래서 AST는 “위치 찾기”에만 사용하고,
    실제 수정은 문자열로 처리했다.

    4. 토큰 없는 색상은 따로 관리

    모든 색상이 토큰에 있는 건 아니다.

    예를 들어, #0b0b0b, #fbbf24 이런 값들은 자동으로 바꾸지 않는다.

    대신,

    {
      "unmatchedColors": {
        "#0b0b0b": { "count": 1 }
      }
    }

    이렇게 따로 모은다.
    이게 중요한 이유는 디자인 시스템 2.0에서 추가해야 할 토큰 후보 목록이 되기 때문이다.

    9. 마무리

    이 프로젝트에서 가장 중요한 변화는
    코드가 아니라 의사결정 방식이었다.

    Before

    • "전체적으로 정리해야 할 것 같다"
    • "좀 복잡하다"

    After

    • "상위 10개 파일부터 보면 된다"
    • "features 영역부터 정리해야 한다"
    • "semantic 토큰 구조를 다시 봐야 한다"

    이 차이는 생각보다 컸다.
    감 -> 데이터 로 바뀌면서 어디서부터 어떻게 개선해야 할지 명확해졌다.

    이 프로젝트의 흐름은 분석 → 시각화 → 자동 변환으로 이어진다.

    • AST 분석: 현재 상태 파악
    • Codemod: 실제 코드 개선

    분석으로 문제를 "보이게" 만들고,
    Codemod로 문제를 "줄일 수 있게" 만들었다.

    9. 마치며

    이 작업을 하면서 느낀 건 하나였다.

    문제를 해결하는 것보다
    문제를 정확히 보는 게 더 어렵다

    디자인 시스템도 마찬가지였다.

    이미 존재하고 있었지만,
    어떻게 쓰이고 있는지는 아무도 몰랐다.

    AST 기반 분석을 통해
    그걸 처음으로 볼 수 있게 만들었다.

    결국엔,

    디자인 시스템을 개선한 게 아니라
    개선할 수 있는 상태를 만든 작업이었다

    Posted inreact
    Written byEunwoo