동적이자 정적인 CSS 생성 플로우 구축하기
tailwindcss는 기본적으로 빌드타임에 코드베이스의 class명을 기준으로 정적인 CSS를 생성하기 때문에 동적인 class명을 대응하는 것이 힘들어요.
그래서 SDUI(Server Driven UI) 환경에서 tailwindcss를 적용하는데 문제가 생겨요.
이번 아티클에서는 SDUI 환경에서 tailwindcss를 사용해야 했던 이유와 동적 class를 정적 CSS로 만들기 위해 적용했던 방법을 소개해보려고 해요.
1. tailwindcss를 채택하기까지
Redot은 하나의 서비스에서 여러 고객사의 웹페이지를 관리/배포하기 위해 SDUI 구조를 사용하고 있어요.
피그마와 유사한 구조의 웹빌더로 웹페이지를 만들고 배포해요.
저희는 빌더에서 홈페이지를 수작업으로 만드는 작업에서 생산성을 높일 수 있을 거라 생각했고, AI 홈페이지 생성 기능을 목적으로 빌더를 설계하게 됐어요.
AI를 활용해 프론트엔드 코드를 생성해보면 대부분 tailwindcss 기반으로 스타일링을 하는 것을 보실 수 있어요.
즉, AI에게 별도 디자인 시스템을 학습시키지 않아도 AI가 가장 잘 이해하고 사용할 수 있는 것이 tailwindcss인 거예요.
그래서 AI와 함께 다양한 성격의 홈페이지를 만들기 위해 tailwindcss 기반으로 디자인 시스템과 웹빌더를 만들게 됐어요.
2. 동적 CSS 생성의 문제
SDUI 특성상 UI 정보가 서버에 저장되기 때문에 코드베이스에서 class명을 추출할 수 없어요.
그래서 tailwindcss의 빌드타임 정적 CSS 생성 기능을 사용할 수 없게 되고, 필요한 CSS가 생성되지 않기 때문에 스타일링이 제대로 적용되지 않는 문제가 생겨요.
초기에는 빠르게 제품을 출시해야 했기 때문에 이와 같은 동적 스타일에 대응하기 위해 자주 사용되는 class명은 safelist에 추가하고, 예외 케이스는 inline style로 대응하도록 했어요.
어느 정도의 홈페이지들에 대해서는 문제가 해결됐지만, 고객사가 많아질수록 다음과 같은 문제들이 생겼어요.
- safelist로 필요 이상의 CSS 생성 및 추가되는 safelist 대응
- AI가 생성하는 class명들에 대해서 예외 스타일을 사람이 수작업으로 대응
그래서
- safelist 없이 고객사별 최적 CSS 제공
- inline style 없이 class명으로 전부 대응
와 같은 목표를 세웠어요.
3. CSS 생성 로직을 직접 만들기로
사실 tailwindcss가 class명 기준으로 정적 CSS를 만드는 과정을 빌더에서 페이지 게시 시점으로 옮기면 해결되는 문제에요. 이렇게 되면 text-[#123456]과 같이 safelist로 대응할 수 없는 스타일도 모두 대응할 수 있게 돼요.
tailwindcss에서는 CSS 생성 기능을 별도로 제공하지 않지만, 오픈소스이기 때문에 빌드타임 CSS 생성 구조를 찾을 수 있었어요.
tailwindcss는 내부적으로 코드베이스의 탐색 후 AST를 만들고 CSS를 추출하는 과정을 거치고 있었어요.
제가 필요한 건 class명 기반 CSS 추출 기능만 필요했고 해당 역할을 하는 함수를 찾아 className 기반 CSS 생성 함수를 만들었어요.
즉, 아래와 같은 형태의 함수가 돼요.
tsimport { __unstable__loadDesignSystem } from '@tailwindcss/node'; async function generateAppCss(classNames: string[]): Promise<string> { const inputCss = `@import "tailwindcss";`; // 디자인 시스템 로드 const designSystem = await __unstable__loadDesignSystem(inputCss, { base: process.cwd(), }); // 추출된 클래스들만 포함된 CSS 생성 const result = designSystem.candidatesToCss(classNames); return result.filter(Boolean).join('');}
결과로 서버에서 UI 정보의 class명 추출 후 해당 함수에 넣으면 CSS를 반환할 수 있게 되었어요.
4. 실제 기능으로 도입
핵심 문제가 해결됐기 때문에 여기부터는 어렵지 않았어요.
우선, 빌더에서 웹페이지 게시 API 호출시 서버단에서 CSS 생성 후 CDN에 저장하는 로직을 추가했어요.
이후 고객 웹페이지에서 CDN의 CSS를 호출함으로써 정상적으로 최적의 CSS가 적용되는 것을 확인할 수 있었어요.
CDN cache 문제 등 사소한 이슈들이 있었지만, 다른 아티클에서 다뤄볼게요.
결론적으로, 코드베이스의 class명 기반이 아닌 서버에 저장된 class명으로 최적의 CSS를 생성하며 모든 동적 스타일에도 대응할 수 있었어요. CSS 용량도 줄이며 성능 또한 챙길 수 있었구요.
그러면서 위와 같이 AI의 홈페이지 생성 기능을 완벽히 사용할 수 있게 되었습니다.