Module federation에서 겪은 tailwind css 이슈 그리고 해결방법
최근 실무를 하면서 Module federation 환경에서 tailwind css를 사용해서 개발을 이어가고 있다.
헤드리스 UI가 부각되면서 Radix UI 기반의 Shadcn UI를 사용하려다보니 tailwind css를 사용하게 되었다.
하지만 역시 시행착오는 있다.
실제 잘 작동할 줄 알았던 스타일링에 문제가 발생했다.
문제 상황
remote 패키지에서 작성한 tailwind css의 class name이 제대로 host 패키지에서 서빙될때 스타일링이 되지 않는 문제
원인 파악
브라우저에서 실제 클래스 네임과 테일윈드의 클래스 네임이 mapping이 잘되는지 확인해보는게 우선이었다.
브라우저에서 스타일링이 정상적이지 않는 요소를 검사해보니 클래스 네임은 들어갔지만 스타일링 mapping이 되지 않았다.
해결 방법
1차 시도
1차라는 말부터 느껴지겠지만 제대로된 해결 방법은 아니다.
먼저 tailwind css의 config 파일 내 content를 의심했다.
tailwind가 버전이 3으로 올라가면서 이제부터 빌드타임때 실제 사용하는 클래스 네임만 전체 tailwind css의 스타일링 시트에서 추출해서 번들사이즈를 줄여주는것으로 알고있었기 때문이다.
content 속성은 실제 tailwind css를 적용시킬 파일 범위를 의미하는것이다.
해당 속성은
./src/**/*.{ts,tsx}
위와 같이 선언되어 있었다.
그래서 host 패키지에서 빌드될때 당연히 다른 패키지의 UI를 범위로 설정하지 않기에 스타일이 제대로 추출되지 않았구나 생각하여 아래와 같이 바꿨다.
../../packages/**/*.{ts,tsx}
변경을 하니 스타일링이 제대로 되는듯 했다.
하지만 이는 문제가 있는 방식이다.
이 내용은 2차 해결에서 후술하겠다.
2차 시도
1차시도에서 해결한 방법이 정답이라고 생각하고 개발을 하다보니
remote 패키지에서 스타일링을 바꾸면 remote 패키지만 빌드해서는 host 패키지에서 서빙하는 UI의 스타일링을 변경할 수 없었다.
remote 패키지를 바꿔도 host 패키지에서 서비스되는 내용이다보니 host 패키지의 tailwind css config의 content 범위에 의해 한번 스타일 추출이 일어나야 스타일이 반영되는 방식으로 해결했던 것이다.
이러면 Module federation을 사용하는 의미가 없어진다고 생각했다.
(각 패키지의 독립성과 빠른 배포가 장점인걸 포기한다는 말과 같다고 느낌)
그렇다면 host 패키지에서 rebuild하지 않고 remote 패키지에 변경된 스타일링을 서비스할 수 있는 방법이 없을까 하다가
아래와 같은 방법이 떠올랐다.
바로 host 패키지에서 애초 모든 tailwind css 스타일 클래스를 추출하여 빌드하는 방법이다.
사실상 버전 2로 돌아가는듯한 방법이었지만 그렇게해서라도 Module federation의 장점을 살리고 싶었다.
방법은 다음과 같다.
safelist: [
{
pattern: /./, // the "." means "everything"
},
],
tailwind css config 파일에 위와 같이 넣게 되면 모든 스타일 클래스를 포함시키겠다는 의미를 가진다.
실제로 빌드해보면 아래와 같이 나온다.
/*
! tailwindcss v3.3.5 | MIT License | https://tailwindcss.com
*//*
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
*/
*,
::before,
::after {
box-sizing: border-box; /* 1 */
border-width: 0; /* 2 */
border-style: solid; /* 2 */
border-color: #e5e7eb; /* 2 */
}
.
.
.
.disabled\:pointer-events-none:disabled {
pointer-events: none;
}
.disabled\:cursor-not-allowed:disabled {
cursor: not-allowed;
}
.disabled\:opacity-50:disabled {
opacity: 0.5;
}
.peer:disabled ~ .peer-disabled\:cursor-not-allowed {
cursor: not-allowed;
}
.peer:disabled ~ .peer-disabled\:opacity-70 {
opacity: 0.7;
}
// 377111 라인
무려 377111라인의 거대한 스타일 덩어리가 튀어나온다...
좀 괴팍한 해결방법이지만 그래도 remote 패키지를 빌드할때마다 host 패키지도 빌드하는 불상사보다는 괜찮겠다 싶었다.
하지만 이것도 문제가 있었는데 3차 해결에서 후술하겠다.
3차 시도
3차시도라고 썼지만 마지막 해결방법이다.
2차시도에서 모든 스타일 클래스를 뽑았기 때문에 remote 패키지 스타일링 문제가 없을거라 생각했지만
className='color-[#ddd] grid grid-col-[10%_auto]'
위와 같이 Arbitrary value라고 하는 커스텀 값 세팅은 기존 tailwind css의 class 스타일 코드에 포함되어 있지 않고 실제 빌드할때 즉성으로 스타일 클래스가 만들어지기 때문에 해당 스타일 클래스 네임은 host 패키지에서 스타일링이 제대로 먹히지 않았다.
그렇다면 뭐가 문제인가라고 생각했을 찰나 여러 깃허브 이슈와 외국 블로그를 살펴보았는데 module federation의 issue에서 해결방법을 찾았다...
메인테이너의 말을 보면 webpack은 remote 패키지에서 expose로 원격 제공할 파일을 선택하면 그 파일이 진입점이 되므로 해당 파일과 연관되어 있는 import문 외에 의존되는 내용은 알 수가 없다는 내용이다.
저걸 보고 정말 머리가 댕하니 울리는듯 했다. 너무나 당연한데 생각을 못하고 있었다.
보통 tailwind css는 main.tsx 또는 App.tsx에서 tailwind css가 포함된 스타일 시트를 불러온 후 작업을 시작하게 된다.
@tailwind base;
@tailwind components;
@tailwind utilities;
위 내용이 포함된 css를 말이다.
(예를 들어 global.css)
그래서 실제 해당 파일을 expose해서 런타임에 통합하기 위해서 remoteEntry.js로 뽑을때 해당 css는 remote 패키지 전역으로 넣어주는 내용이기 때문에 module federation 입장 그리고 해당 파일만 봤을때는 전혀 상관이 없는 스타일 시트인 것이다.
따라서 다음과 같이 해결했다.
remote 패키지에서 expose할 스타일링 된 컴포넌트 또는 페이지에서 remote 패키지만 보면 불필요한 import문을 작성해주었다.
import '@style/global.css'
...
저렇게 import문을 넣어주게 되면 expose 한 파일을 webpack이 remoteEntry.js로 번들링할때 해당 스타일 코드를 참조하는 css 파일을 같이 불러오게 되어 host 패키지에서 remote 패키지 내용이 잘 서비스 된다.
그렇다 돌고 돌아왔지만 tailwind css config를 수정할게 아니라 위 부분만 넣어주면 되는것이었다.
기존에 변경했던 tailwind css config를 원복하고 모든 remote 패키지에 스타일링이 필요한 expose 파일에 tailwind 스타일 시트를 import해서 넣어주어 해결했다.
최종 모습을 보면 module federation의 원칙과 같이 remote 패키지에서 작성한 스타일링이 host 패키지의 어떤 부분과도 의존하지 않고 remote 패키지 자체적으로 UI, 로직, 스타일링이 제공된다는 점을 알 수 있다.
결론
현재는 3차시도의 방법으로 개발을 잘 이어가고 있다.
물론 expose 하는 UI 파일에 tailwind 스타일링 css를 import 해주는게 귀찮기는 하나
이만큼 단순명료한 해결법이 더 이상 떠오르지 않았다.
이 단순한 해결책을 위해 시간을 낭비하고 돌아오기도 했지만 덕분에 tailwind css에 대한 지식이 늘어서 나쁘지 않은 교환이었다 생각해본다.
비고
https://github.com/module-federation/module-federation-examples/issues/1738
https://github.com/module-federation/module-federation-examples/discussions/714