detecting-classes-in-source-files

detecting-classes-in-source-files (소스 파일에서 클래스 탐지)

Tailwind가 프로젝트의 소스 파일을 스캔해서 실제로 사용된 유틸리티 클래스를 찾아내는 원리를 설명하는 문서예요. 옆에서 함께 짚어 드릴게요.

출처: 문서

본문

Overview

Tailwind는 프로젝트에서 유틸리티 클래스를 스캔한 다음, 실제로 사용한 클래스를 기반으로 필요한 모든 CSS를 생성하는 방식으로 동작해요.

이 덕분에 CSS를 최대한 작게 유지할 수 있고, 임의 값(arbitrary values) 같은 기능도 가능해져요.

How classes are detected

Tailwind는 모든 소스 파일을 평범한 텍스트로 취급하며, 파일을 코드로 파싱하려고 시도하지 않아요.

대신 Tailwind가 클래스 이름에서 기대하는 문자들을 바탕으로, 파일 안에서 클래스가 될 수 있는 토큰을 찾아요.

export function Button({ color, children }) {
  const colors = {
    black: "bg-black text-white",
    blue: "bg-blue-500 text-white",
    white: "bg-white text-black",
  };

  return (
    <button
      className={`${colors[color]} rounded-full px-2 py-1.5 font-sans text-sm/6 font-medium shadow`}
    >
      {children}
    </button>
  );
}

그런 다음 이 모든 토큰에 대한 CSS를 생성하려 시도하고, 프레임워크가 아는 유틸리티 클래스에 매핑되지 않는 토큰은 버려요.

Dynamic class names

Tailwind는 소스 파일을 평문으로 스캔하기 때문에, 여러분이 쓰는 프로그래밍 언어의 문자열 연결(concatenation)이나 보간(interpolation)을 이해할 방법이 없어요.

Don't construct class names dynamically

<div class="text-{{ error ? 'red' : 'green' }}-600"></div>

위 예시에서 text-red-600 이나 text-green-600 문자열은 존재하지 않기 때문에, Tailwind는 이 클래스들을 생성하지 않아요.

대신 사용하는 클래스 이름이 완전한 형태로 존재하는지 확인해 주세요.

Always use complete class names

<div class="{{ error ? 'text-red-600' : 'text-green-600' }}"></div>

React나 Vue 같은 컴포넌트 라이브러리를 쓴다면, props로 클래스를 동적으로 구성해서는 안 된다는 뜻이에요.

Don't use props to build class names dynamically

function Button({ color, children }) {
  return <button className={`bg-${color}-600 hover:bg-${color}-500 ...`}>{children}</button>;
}

대신 props를 빌드 타임에 정적으로 감지할 수 있는 완전한 클래스 이름에 매핑하세요.

Always map props to static class names

function Button({ color, children }) {
  const colorVariants = {
    blue: "bg-blue-600 hover:bg-blue-500",
    red: "bg-red-600 hover:bg-red-500",
  };

  return <button className={`${colorVariants[color]} ...`}>{children}</button>;
}

이렇게 하면 예를 들어 서로 다른 prop 값들을 서로 다른 색상 음영에 매핑할 수 있는 추가적인 이점도 있어요.

function Button({ color, children }) {
  const colorVariants = {
    blue: "bg-blue-600 hover:bg-blue-500 text-white",
    red: "bg-red-500 hover:bg-red-400 text-white",
    yellow: "bg-yellow-300 hover:bg-yellow-400 text-black",
  };

  return <button className={`${colorVariants[color]} ...`}>{children}</button>;
}

코드에서 항상 완전한 클래스 이름을 사용하기만 하면, Tailwind는 매번 모든 CSS를 완벽하게 생성해 줘요.

Which files are scanned

Tailwind는 다음 경우를 제외하고 프로젝트의 모든 파일에서 클래스 이름을 스캔해요.

  • .gitignore 파일에 있는 파일
  • node_modules 디렉토리의 파일
  • 이미지, 동영상, zip 파일 같은 이진 파일
  • CSS 파일
  • 흔한 패키지 매니저 잠금(lock) 파일

Tailwind가 기본적으로 무시하는 파일들을 스캔해야 한다면, 해당 소스들을 명시적으로 등록할 수 있어요.

Explicitly registering sources

@source 를 사용해서 스타일시트를 기준으로 한 소스 경로를 명시적으로 등록할 수 있어요.

@import "tailwindcss";
@source "../node_modules/@acmecorp/ui-lib";

의존성은 보통 .gitignore 파일에 나열되어 Tailwind가 기본적으로 무시하므로, Tailwind로 빌드된 외부 라이브러리를 스캔해야 할 때 특히 유용해요.

Setting your base path

Tailwind는 기본적으로 현재 작업 디렉토리(current working directory)를 클래스 이름 스캔의 시작점으로 사용해요.

소스 탐지의 기본 경로를 명시적으로 설정하려면, CSS에서 Tailwind를 import할 때 source() 함수를 사용해요.

@import "tailwindcss" source("../src");

빌드 명령이 각 프로젝트 루트가 아닌 모노레포 루트에서 실행되는 모노레포에서 작업할 때 유용할 수 있어요.

Ignoring specific paths

@source not 을 사용해서 클래스 이름 스캔 시 스타일시트를 기준으로 한 특정 경로를 무시할 수 있어요.

@import "tailwindcss";
@source not "../src/components/legacy";

레거시 컴포넌트나 서드파티 라이브러리처럼 Tailwind 클래스를 쓰지 않는다고 아는 큰 디렉토리가 프로젝트에 있을 때 유용해요.

Disabling automatic detection

모든 소스를 명시적으로 등록하고 싶다면 source(none) 을 사용해서 자동 소스 탐지를 완전히 끌 수 있어요.

@import "tailwindcss" source(none);
@source "../admin";
@source "../shared";

여러 개의 Tailwind 스타일시트가 있는 프로젝트에서 각 스타일시트가 필요한 클래스만 포함하도록 하고 싶을 때 유용할 수 있어요.

Safelisting specific utilities

콘텐츠 파일에 없지만 Tailwind가 특정 클래스 이름을 생성하도록 강제해야 한다면, @source inline() 을 사용해서 생성하도록 할 수 있어요.

@import "tailwindcss";
@source inline("underline");

생성된 CSS:

.underline {
  text-decoration-line: underline;
}

Safelisting variants

@source inline() 을 사용해서 변형(variant)이 붙은 클래스를 생성할 수도 있어요. 예를 들어 underline 클래스를 hover와 focus 변형과 함께 생성하려면, 소스 입력에 {hover:,focus:,} 를 추가하면 돼요.

@import "tailwindcss";
@source inline("{hover:,focus:,}underline");

생성된 CSS:

.underline {
  text-decoration-line: underline;
}

@media (hover: hover) {
  .hover\:underline:hover {
    text-decoration-line: underline;
  }
}

@media (focus: focus) {
  .focus\:underline:focus {
    text-decoration-line: underline;
  }
}

Safelisting with ranges

소스 입력은 중괄호 확장(brace expansion)되므로 여러 클래스를 한 번에 생성할 수 있어요. 예를 들어 hover 변형이 붙은 모든 빨간 배경색을 생성하려면, 범위를 사용하면 돼요.

@import "tailwindcss";
@source inline("{hover:,}bg-red-{50,{100..900..100},950}");

생성된 CSS:

.bg-red-50 {
  background-color: var(--color-red-50);
}
.bg-red-100 {
  background-color: var(--color-red-100);
}
.bg-red-200 {
  background-color: var(--color-red-200);
}
/* ... */
.bg-red-800 {
  background-color: var(--color-red-800);
}
.bg-red-900 {
  background-color: var(--color-red-900);
}
.bg-red-950 {
  background-color: var(--color-red-950);
}

@media (hover: hover) {
  .hover\:bg-red-50:hover {
    background-color: var(--color-red-50);
  }
  /* ... */
  .hover\:bg-red-950:hover {
    background-color: var(--color-red-950);
  }
}

이것은 빨간 배경색을 100부터 900까지 100 단위로, 그리고 첫 번째와 마지막 음영인 50과 950을 생성해요. 또한 그 각각의 클래스에 hover: 변형도 추가해요.

Explicitly excluding classes

@source not inline() 을 사용하면 소스 파일에서 감지되더라도 특정 클래스가 생성되지 않도록 막을 수 있어요.

@import "tailwindcss";
@source not inline("{hover:,focus:,}bg-red-{50,{100..900..100},950}");

이렇게 하면 빨간 배경 유틸리티와 그 hover·focus 변형이 생성되지 않도록 명시적으로 제외해요.

더 알아보기 (Learn more)