개발 일기

[Tailwind CSS] 1. 테일윈드의 동작방식과 동적 스타일링

Kody_with_the_K 2024. 11. 9. 00:37

[Tailwind CSS] 1. 테일윈드의 동작방식과 동적 스타일링

Tailwind CSS는 효율적인 스타일링을 가능하게 하는 유틸리티 퍼스트 프레임워크다.

특히, 제로 런타임(Zero Runtime) 특성을 지니고 있어 별도의 JavaScript나 런타임 처리가 필요하지 않다. 모든 스타일은 컴파일 시점에 생성되며, 브라우저에서 별도의 런타임 비용 없이 즉시 적용된다. 이는 성능 향상과 더불어 복잡한 스타일 로직 없이도 효율적인 스타일링을 가능하게 한다. 그러나 Tailwind는 정적 분석(static analysis) 방식을 사용하기 때문에 동적으로 클래스를 할당할 경우 제한이 발생할 수 있다.

 

이 글에서는 Tailwind CSS의 동작 방식을 분석하고, 동적 클래스 할당 시 발생하는 문제가 발생하는 이유와 이를 해결하는 방법에 대해 알게된 점들에 대해 설명한다.

 

Tailwind CSS의 동작 방식 이해

Tailwind CSS는 유틸리티 퍼스트(Utility-First) 접근 방식을 채택하여, 미리 정의된 클래스들을 조합하여 원하는 스타일을 구현할 수 있게 한다. 이는 CSS를 직접 작성하지 않고도 빠르게 스타일링을 적용할 수 있는 장점을 제공한다. Tailwind의 동작 방식은 다음과 같은 주요 단계를 포함한다:

 

1. 정적 분석(Static Analysis)

Tailwind는 프로젝트의 소스 파일(예: HTML, JSX, Vue, Svelte 등)을 스캔하여 사용된 클래스 이름을 추출한다. 이 과정에서 정규표현식을 사용하여 클래스 이름을 식별하고, 이를 기반으로 필요한 CSS를 생성한다. 예를 들어, 다음과 같은 클래스가 사용되었다면:

<div class="text-red-600 bg-blue-500 p-4"></div>

Tailwind는 text-red-600, bg-blue-500, p-4 클래스를 인식하고, 해당 스타일을 CSS로 변환한다.

 

2. 제로 런타임(Zero Runtime)

Tailwind는 제로 런타임 프레임워크로, 스타일을 적용하기 위해 별도의 JavaScript나 런타임 처리가 필요 없다. 모든 스타일은 빌드 시점에 생성되며, 브라우저는 이를 즉시 적용한다. 이로 인해 다음과 같은 이점이 있다:

성능 향상: 런타임에서 스타일을 처리하지 않기 때문에 초기 로딩 속도가 빨라진다.

간결한 코드: 복잡한 스타일 로직 없이도 필요한 스타일을 클래스 조합만으로 구현할 수 있다.

유지보수 용이: 일관된 유틸리티 클래스 사용으로 코드의 가독성과 유지보수성이 향상된다.

 

3. CSS 생성 과정

Tailwind는 빌드 도구(예: PostCSS)를 통해 CSS를 생성한다. tailwind.config.js 파일을 통해 사용자 정의 설정을 적용할 수 있으며, 여기서 테마, 색상, 간격 등 다양한 설정을 커스터마이징할 수 있다. Tailwind는 필요한 클래스들만을 포함한 CSS 파일을 생성하여, 최종 번들 크기를 최소화한다.

 

4. Just-In-Time (JIT) 모드

JIT(Just-In-Time) 모드를 지원하여, 필요한 클래스만을 실시간으로 생성한다. JIT 모드는 다음과 같은 장점을 제공한다:

빠른 빌드 속도: 필요한 클래스만을 생성하므로 빌드 시간이 단축된다.

확장된 클래스 패턴 지원: 동적 클래스 생성 시 더 많은 패턴을 지원하여 유연성을 높인다.

CSS 파일 크기 감소: 사용되지 않는 클래스가 제거되어 최종 CSS 파일의 크기가 줄어든다.

JIT 모드를 활성화하려면 tailwind.config.js 파일에 다음과 같이 설정한다:

// tailwind.config.js
module.exports = {
  mode: 'jit',
  purge: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'],
  // 추가 설정
};

 

5. Purge 과정

Purge 과정은 사용되지 않는 CSS 클래스를 제거하여 최종 CSS 파일의 크기를 줄이는 역할을 한다. purge 옵션을 통해 Tailwind는 어떤 파일을 스캔할지 지정하며, 이 과정에서 실제로 사용되는 클래스들만 남기게 된다.

 

 

동적 클래스 할당의 문제점

Tailwind는 이렇게 정적 분석을 기반으로 작동하기 때문에, 동적으로 생성된 클래스 이름을 인식하지 못할 수 있다. 예를 들어, React 컴포넌트에서 템플릿 리터럴을 사용하여 클래스를 동적으로 생성할 경우 문제가 발생한다.

// 동적 클래스 할당 (동작하지 않음)
<div className={`grid-cols-${columns}`}></div>

여기서 columns는 상태나 props에 따라 변하는 값이다. 예를 들어, columns가 3이라면 최종 클래스는 grid-cols-3이 된다. 그러나 Tailwind는 빌드 시점에 grid-cols-3이라는 정확한 클래스 이름을 알지 못해 해당 스타일을 생성하지 않는다. 결과적으로, 동적으로 생성된 클래스는 적용되지 않아 원하는 레이아웃을 얻지 못하게 된다.

 

왜 grid-cols-${columns}가 동작하지 않는가?

Tailwind의 정적 분석은 완전한 클래스 이름을 필요로 한다. 템플릿 리터럴을 사용하여 동적으로 생성된 클래스는 빌드 시점에 Tailwind가 해당 클래스를 인식하지 못하기 때문에 필요한 CSS가 생성되지 않는다. 이는 다음과 같은 예시에서 명확히 드러난다.

// 동적 클래스 할당 (동작하지 않음)
<div className={`text-${error ? "red" : "green"}-600`}></div>

// 올바른 클래스 할당 (동작함)
<div className={`${error ? "text-red-600" : "text-green-600"}`}></div>

첫 번째 예시는 text-red-600 또는 text-green-600을 동적으로 생성하지만, Tailwind은 이를 정적으로 인식하지 못한다. 반면 두 번째 예시는 클래스 이름을 완전히 명시적으로 작성하여 Tailwind가 이를 올바르게 인식하고 CSS를 생성할 수 있게 한다.

 

Tailwind CSS에서 동적 스타일링 해결 방법

Tailwind CSS를 사용할 때 동적 클래스를 적용하기 위한 몇 가지 효과적인 방법이 있다. 아래에서는 React를 기준으로 다양한 접근 방식을 소개한다.

1. 조건부 클래스 할당

가장 직관적인 방법은 조건부 연산자를 사용하여 완전한 클래스 이름을 조건에 따라 할당하는 것이다.

const MyComponent = ({ isImportant }) => {
  return (
    <div
      className={`${isImportant ? "text-red-600 text-lg font-bold" : "text-black"}`}
    >
      중요한 메시지
    </div>
  );
};

이 예시에서 isImportant가 true라면 text-red-600, text-lg, font-bold 클래스가 적용되고, false라면 text-black 클래스가 적용된다.

 

2. safelist 옵션 사용

Tailwind의 safelist 옵션을 활용하면 빌드 시점에 동적으로 생성될 수 있는 클래스들을 미리 지정할 수 있다. 이를 통해 Tailwind이 해당 클래스를 인식하고 CSS를 생성하도록 할 수 있다.

// tailwind.config.js
module.exports = {
  // ...
  safelist: [
    {
      pattern: /^grid-cols-/,
    },
  ],
  // ...
};

위 설정을 추가하면 grid-cols-1부터 grid-cols-12까지의 모든 클래스가 빌드에 포함된다.

const GridComponent = ({ columns }) => {
  return <div className={`grid grid-cols-${columns}`}>그리드 레이아웃</div>;
};

이제 columns가 변경되어도 Tailwind은 해당 클래스를 인식하고 올바르게 스타일을 적용할 수 있다.

 

3. 미리 정의된 클래스 사용

가능한 경우, 미리 정의된 클래스들을 조건에 따라 선택적으로 적용하는 방법도 고려해보자.

const GridComponent = ({ columns }) => {
  const columnsClass = {
    1: 'grid-cols-1',
    2: 'grid-cols-2',
    3: 'grid-cols-3',
    4: 'grid-cols-4',
    // 필요한 만큼 추가
  };

  return <div className={`grid ${columnsClass[columns]}`}>그리드 레이아웃</div>;
};

이 접근 방식은 Tailwind의 정적 분석을 우회하여 동적 클래스를 안전하게 사용할 수 있게 한다.