Open Source

JS to CSS 변환 시, 값이 0인 변수의 단위 누락 수정

2026년 1월 5일

StyleX 오픈소스 기여 과정에서 발견한 CSS 변수 단위 누락 문제와 이를 해결하기 위해 Babel 플러그인 로직을 수정한 과정을 공유합니다.

개요

StyleX는 컴파일 타임에 스타일을 최적화된 CSS로 변환해주는 라이브러리입니다.
이 과정에서 CSS 파일의 크기를 줄이기 위해 불필요한 단위를 제거하는 최적화가 수행되는데,
로직이 CSS 변수에도 잘못 적용되어 레이아웃이 깨지는 문제가 있었습니다.


문제 상황

StyleX를 사용하여 calc() 함수 내에서 CSS 변수를 활용할 때 문제가 발생했습니다.

import * as stylex from '@stylexjs/stylex';

const styles = stylex.create({
  container: {
    "--header-height": '0px',
    minHeight: 'calc(100dvh - var(--header-height, 0px))',
  },
});

export default function Example() {
  return <div className={...stylex(styles.container)}>Content</div>;
};

빌드 시, styles.container에 정의된 --header-height의 값 0px이 최적화 과정에서 단위가 제거되어 0으로 변환되었습니다.

CSS의 calc() 함수 내에서 덧셈이나 뺄셈을 할 때, 단위가 없는 숫자(0 포함)와 단위가 있는 숫자 간의 연산은 불가능합니다.
이로 인해 100dvh - 0 처리가 되지 않아 minHeight 속성 자체가 무효화되었고, 레이아웃이 의도한 대로 표시되지 않는 문제가 발생했습니다.


원인 분석: Babel Plugin과 AST

이 문제는 StyleX의 Babel Plugin이 소스 코드를 변환하는 과정에서 발생했습니다.

Babel Plugin이란?

Babel은 자바스크립트 컴파일러로, 최신 문법을 구형 브라우저 호환 코드로 변환하거나 코드 자체를 분석해 다른 형태로 가공하는 데 사용됩니다.
StyleX는 Babel 플러그인을 통해 자바스크립트 객체로 작성된 스타일을 AST(추상 구문 트리) 형태로 분석하고, 이를 정적 CSS 파일로 추출합니다.

문제의 코드 (normalizeZeroDimensions)

분석 결과, StyleX 컴파일러 내부의 normalizeZeroDimensions 함수에서 CSS 값을 최적화하기 위해
AST를 순회하며 값이 0인 경우 단위를 삭제하는 로직이 문제였습니다.

// (수정 전) 값이 0인 경우 단위를 삭제하는 로직
export default function normalizeZeroDimensions(
  ast: PostCSSValueAST,
  key: mixed,
): PostCSSValueAST {

  let endFunction = 0;

  ast.walk((node) => {
    // ... (함수 내부인지 확인하는 로직) ...

    // 값이 0이면 단위를 검사
    const dimension = parser.unit(node.value);
    if (!dimension || dimension.number !== '0') {
      return;
    }

    // 각도(deg), 시간(s) 등 특정 단위는 남기고, 나머지는 단위를 제거
    if (angles.indexOf(dimension.unit) !== -1) {
      node.value = '0deg';
    }
    // ... (중략) ...
    else if (!endFunction) {
      node.value = '0'; // <- 여기서 '0px'이 '0'으로 변경됨
    }
  });
  return ast;
}

일반적인 CSS 속성(예: margin: 0px)에서는 단위가 없어도(margin: 0) 문제가 없지만,
CSS 변수는 calc() 계산식 등에서 엄격한 단위가 요구됩니다.


해결 방법

현재 처리 중인 속성이 CSS 변수(Custom Property)인 경우에는 이 "0 단위 제거 최적화"를 수행하지 않도록 예외 처리를 했습니다.
CSS 변수는 항상 --로 시작한다는 규칙을 이용하여, 함수 초입에 키(key)를 검사하는 로직을 추가했습니다.

// (수정 후) CSS 변수인 경우 최적화 건너뛰기
export default function normalizeZeroDimensions(
  ast: PostCSSValueAST,
  key: mixed,
): PostCSSValueAST {
  // 변경 사항: 키가 문자열이고 '--'로 시작하면 원본 AST를 그대로 반환
  if (typeof key === 'string' && key.startsWith('--')) {
    return ast;
  }

  let endFunction = 0;

  ast.walk((node) => {
    // ... 기존 로직 수행 ...
  });
  return ast;
}

이 코드를 적용하면 --header-height: 0px과 같은 변수를 만났을 때,
normalizeZeroDimensions 함수가 아무런 변환 없이 바로 리턴하므로 0px 의 단위가 안전하게 보존됩니다.


느낀점

취준 기간이 길어지면서 개발자로써 가치를 만들고 싶어 오픈 소스 기여를 결심했습니다.
이번 기여를 통해 사람들이 즐겨 사용하는 서비스(instagram)에 약소하게 기여했다는 성취감을 얻을 수 있었습니다.
이후에도, 다양한 오픈 소스 프로젝트들을 기여해보면서 저의 가치를 증명해 나갈 예정입니다.