인터랙션 패키지 현대화

인터랙션 패키지: Emotion 의존성 제거와 inline style 전환

TL;DR

서비스에서는 더 이상 Emotion을 직접 쓰지 않았지만 인터랙션 패키지 내부 구현이 Emotion에 의존했고 peerDependencies로도 노출돼 있었어요. 그러다 보니 이 패키지를 쓰는 서비스들이 Emotion을 완전히 떼어내기 어려웠어요. 그래서 패키지의 styling 의존성을 정리하려고 Emotion 제거와 inline style 전환을 진행했어요.

문제 정의 Problem

인터랙션 패키지는 모든 토스 서비스가 함께 쓰는 공용 패키지였어요. 그래서 내부 구현을 어떻게 고르느냐가 패키지 안에만 머물지 않고 이 패키지를 쓰는 서비스의 의존성에까지 영향을 줬어요. 서비스 쪽은 이미 Emotion을 더 안 쓰는 방향으로 정리되던 중이었지만, 공용 패키지의 peer dependency는 쓰는 쪽으로 번지는 계약이라 인터랙션 패키지가 Emotion을 요구하는 한 소비 서비스의 발목을 잡았어요. 더는 필요 없는 styling runtime 의존성을 덜어내 패키지의 독립성을 높여야 했어요.

접근 방식 Approach

외부 styling library 의존성을 새로 만들지 않는 방향을 우선 기준으로 두고, Emotion 스타일 코드를 React inline style 기반으로 옮겼어요. 인터랙션 패키지가 소비 서비스에 Emotion 제공을 요구하지 않도록 styling runtime 의존성을 패키지 계약에서 제거하는 것을 변경 범위로 잡았어요. Emotion을 다른 라이브러리로 단순히 바꾸는 대신, 공용 패키지에 새로운 styling toolchain 의존성을 추가하는 게 맞는지 먼저 검토했어요.

복잡한 selector 문법을 많이 쓰지 않는 패키지 특성을 고려해, 외부 styling library 없이 동작할 수 있는 inline style 기반 구조를 선택했어요. Emotion 제거 요청을 받고서, 기술 선택지 검토부터 구현과 릴리즈까지 맡았어요. 처음엔 vanilla-extract(VE)를 살펴봤지만, 우리 배포 구조에서는 소비 서비스나 TDS 쪽 빌드 설정과 버전 호환성까지 같이 고려해야 했어요.

Emotion으로 작성된 스타일 중 selector나 pseudo state 의존이 강한 부분이 많지 않은지 확인했어요. 대부분의 스타일을 React inline style로 옮기고, Emotion 기반 구현과 package.json의 peerDependencies 항목을 함께 제거했어요. TDS 레거시 대응 코드는 TDS 담당 팀과 논의해 새 major 버전부터는 지원하지 않기로 정리했어요. Storybook에서 여러 인터랙션 컴포넌트 케이스를 확인한 뒤 major 업데이트로 릴리즈했고, 하위 호환 변경 사항은 마이그레이션 문서에 적어 뒀어요.

핵심 결정 Decision

vanilla-extract 대신 inline style로 바꿨어요. 외부 styling library 의존성을 최소한으로 줄이기 위한 결정이었어요. Emotion을 제거한 목적이 공용 패키지의 의존성 경계를 단순하게 만드는 것이었던 만큼, 다른 styling library로 갈아타는 것만으로는 문제를 충분히 풀었다고 보기 어려웠어요.

inline style은 selector, pseudo state, media query처럼 CSS가 자연스럽게 다루는 표현을 직접 다루기 까다롭다는 한계가 있어요. 다만 인터랙션 패키지에서는 그런 문법을 많이 쓰지 않았고 패키지를 더 독립적으로 유지한다는 장점이 더 컸어요. 앞서 정리한 TDS 레거시 처리 방침대로, 그 레거시 대응 코드를 계속 끌고 가면 Emotion 제거가 어려웠고 그 목표와도 맞지 않았어요. 그래서 하위 호환을 일부 포기하는 변경이 됐어요.

해결 결과 Result

인터랙션 패키지 안에서 Emotion 기반 스타일 구현을 걷어냈고 peerDependencies에서도 Emotion을 제거했어요. 인터랙션 패키지를 의존하던 서비스나 다른 패키지에서도 Emotion을 떼어낼 수 있는 상태가 됐어요. 스타일링 방식이 특정 runtime library에 묶이지 않게 되면서 인터랙션 패키지를 더 독립적인 공용 패키지로 끌고 갈 여지가 생겼어요.

배운 점 Learning

이 작업을 하면서 공용 패키지의 의존성은 단순한 구현 선택이 아니라 그 패키지를 쓰는 서비스로 번지는 계약이라는 걸 다시 한번 느꼈어요. 특히 peerDependencies는 내부에서만 쓰는 기술이 아니라 쓰는 쪽에도 영향을 주는 결정이라, 더 조심해서 골라야 했어요. zero-runtime library라고 해서 늘 공용 패키지에 맞는 선택은 아니라는 것도 배웠어요. 런타임 비용이 없더라도 빌드 설정, 버전 호환성, toolchain 의존성이 따라붙는다면, 패키지의 역할에 따라 더 단순한 inline style이 나은 선택일 수 있었어요.