요즘 저는 atomic-css 방법론을 적극적으로 사용하고 있습니다. xxx-wrapper, xxx-container 와 같은 css 이름 작명을 완전히 벗어날 수 있다는 장점 하나만으로도 충분히 만족하고 있기 때문입니다. 그리고 atomic-css 도구 중 unocss를 사용하고 있습니다.
unocss는 꽤나 쓸만합니다. tailwind-css, windi-css와 달리 unocss는 아직 1.0 버전이 나오지 않았지만, 충분한 기능을 제공하고 있기 때문에 실무에서 사용하고 있습니다. 대표적으로 가장 만족하는 점은 커스텀하기 쉽다는 것과 멋진 inspector를 제공한다는 점입니다.
일단 설명하기 앞서, unocss scope을 지정한다는 것은 동적으로 생성된 유틸리티 css들이 적용되는 범위를 한정짓는 것을 의미합니다. 그럼 범위를 한정짓는게 어떤 경우에 필요할까요? 다른 프로젝트에서 적용될 수 있는 "컴포넌트 라이브러리"를 만드는 경우에 scope를 적용하면 편하게 작업할 수 있습니다.
일반적으로 컴포넌트 라이브러리를 만들때 css를 global 범위로 적용되게 하면 상위 컴포넌트에 문제를 일으킬 수 있습니다. 그래서 해당 컴포넌트에만 적용될 수 있게 css-module 방식을 사용하거나 bem 방법을 사용해 css를 만듭니다.
// 우리는 컴포넌트 라이브러리를 사용하다보면 종종 이런 class 이름들을 확인할 수 있습니다.
.Toastify__toast {...}
.Toastify__toast-container { ... }
그러나 제가 만들어야하는 컴포넌트는 규모가 매우 컸습니다. iframe 없이 embeddable한 web-app을 만드는게 목표였습니다. bem 방식을 적용하기엔 너무 부담이 컸고, css-module 방식을 적용하기엔 css 번들 사이즈를 줄일 수 없는 단점이 있었습니다. 그래서 저는 unocss를 사용해 번들 사이즈도 줄이면서 상위 컴포넌트의 css에 영향을 주지 않고자 scope를 적용했습니다.
물론 prefix를 두어 상위 컴포넌트에 영향을 주지 않을 수 있습니다. 그러나 유틸리티 클래스를 작성할때마다 prefix를 붙이는게 더 번거롭기도 하고, prefix를 변경할 때마다 수백개의 class를 수정해야하거나, prefix를 무조건 붙여야 유틸리티 클래스가 생성되는 등 불편한 점이 많기에 scope를 적용하기로 했습니다.
서두가 길었습니다. unocss에 scope을 만드는건 단순합니다. vite.config.js 파일에 아래와 같이 variants 필드를 수정하면 됩니다.
// vite.config.js
export default defineConfig({
plugins: [
unocss({
variants: [
function (matcher) {
return {
matcher,
selector: s => `${root classname or id} $$ ${s}`,
};
},
],
}),
],
});
이렇게 하면 생성되는 유틸리티 class는 모두 다음과 같이 변경됩니다.
// before
.w-100%{width:100%}
.h-100%{height:100%}
// after
// root dom classname을 'my-component-root'라 가정
.my-component-root .w-100%{width:100%}
.my-component-root .h-100%{height:100%}
일반적으로 unocss에 scope를 적용하는 일은 거의 없을 겁니다. 그러나 좀 더 복잡한 컴포넌트 라이브러리를 만드는 경우, css-module, bem 방법론 말고도 scope를 지정하면 atomic-css 방법도 적용할 수 있다는 점을 알리고 싶었습니다. 부족한 부분이 있다면 지적 부탁드리며, 읽어주셔서 감사합니다.