ClojureScript에서 CSS module 사용하기

Jeongbong Seo
bgpworks
Published in
5 min readOct 5, 2020

shadow-cljs와 postcss-module을 활용하여 ClojureScript 프로젝트에 CSS module 적용한 방법.

Photo by Pankaj Patel on Unsplash

CSS module를 이용하면 CSS 클래스 이름 중복으로 인한 버그를 줄일 수 있어서 웹개발에서 널리 쓰이고 있다. Webpack 같은 유명 번들러에는 플러그인이 잘 만들어져 있어서 손쉽게 적용해볼 수 있다.

아쉽게도 ClojureScript는 언어를 사용하는 사람이 적어서인지 잘 만들어진 솔루션을 찾지 못했다. 어쩌면 Hiccup 스타일이 워낙 편해서 DOM id와 css class이름을 적당히 섞어쓰면 이름 충돌 문제도 어느정도 피할 수 있기 때문에 아직 만든 사람이 없는게 아닐까 싶다. 적어도 필자는 저 이유로 큰 불편함 없이 쓰고 있었다. 그러다 프로젝트 크기가 점차 커짐에 따라 컴포넌트를 좀 더 안전하게 모듈화하고 싶은 욕심이 생겨서 실험적으로 CSS Module을 도입해보기로 하였다. 다행히 shadow-cljs와 postcss를 조합하여 완전하진 않지만 만족스러운 수준으로 원하는 기능을 구현할 수 있었다. 다른 비주류 언어에서도 비슷하게 적용해 볼 수 있을 것 같다.

목표

CSS Module을 적용하되, 개발 편의성은 해치고 싶지 않았기 때문에 아래와 같은 목표를 세웠다.

  • 바퀴를 새롭게 개발하고 싶진 않다. 아이디어를 참고하여 새로 구현하는 일은 피하고 싶다.
  • Reagent (Hiccup 스타일)로 짜여진 기존 코드 스타일에 큰 변화가 없었으면 한다.
  • shadow-cljs가 제공하는 Hot reload 기능과 부드럽게 연동되었으면 한다.
  • Less를 사용하고 있었지만 간단한 기능만 사용하고 있었다. Less나 Sass가 아니어도 되니 Nesting, 변수, mixin 정도는 계속 사용할 수 있었으면 한다.

결과

안드로이드 스타일을 참고하여 css module 파일을 컴파일하여 로컬 클래스명 → 실제 클래스명 맵핑을 가지는 cljs 파일을 생성하도록 했다. UI에서 클래스명을 입력할 때에는 생성된 cljs 파일을 참조하도록 했다. 끝! 구조가 간단한 만큼 shadow-cljs의 기본 기능들과 연동도 잘 되었다.

간략화한 빌드 구조. postcss를 이용하여 css파일을 컴파일하여 통합 css파일과 클래스명 맵핑 cljs 파일을 생성한다. 이후는 shadow-cljs를 사용하는 일반적인 ClojureScript 프로젝트와 동일하다.

클래스 이름 맵핑 파일: CLJS vs JSON

postcss-module의 기본 동작은 클래스 이름 맵핑을 JSON 으로 생성한다. shadow-cljs의 shadow.resource/inline 함수를 사용하면 CLJS에서 JSON을 직접 참조하여 사용할 수 있지만 (심지어 참조한 파일의 변경을 자동으로 감지하여 컴파일도 해준다!), 클래스명 참조 오타를 컴파일 시점에 잡고싶어서 번거롭지만 CLJS 파일을 생성하고 CLJS 변수를 참조하는 방식을 사용했다. 아쉬운 점은 CSS 로컬 클래스이름으로 classname, sort 등을 사용하여 ClojureScript의 전역변수와 이름이 겹칠 때인데 ClojureScript가 아직 (:refer-clojure :only [def]) 를 지원하지 않아서 CLJS파일을 생성할 때 :exclude로 충돌나는 이름을 일일이 빼줘야 한다.

한계점

Webpack의 잘 만들어진 플러그인에 비교하면 아쉬운 점들이 여러가지가 있다.

CSS파일 수정 시 화면 깜빡임 문제

CSS 파일을 수정하면 클래스 이름이 새롭게 생성되고 결과물 CSS 파일이 바뀌는데, 이 두 동작이 동기화 되어 있지 않다보니 화면 전체에 스타일이 잠시동안 사라지는 문제가 발생한다. 먼저 클래스 매핑 CLJS 파일이 바뀌면서 화면이 새롭게 그려지는데, 아직 새로운 클래스 이름들의 CSS 정의는 생성되지 않았기 때문에 하면 전체의 CSS가 날아가게 된다. 이 후 CSS 컴파일이 완료되고 리로드 되면서 다시 스타일이 적용되는 식이다. 이 문제를 피하려면 Webpack의 style-loader 같은 방식으로 페이지가 새로 그려질 때 스타일 정의도 같이 동기화되어 로드되는 방식으로 개선해야 할 것 같다.

부가적인 컴파일 프로세서를 병행해야 하는 문제

Webpack은 JS에서 CSS 파일을 직접 참조하는 형태이고, 참조하는 모듈 동작을 플러그인으로 커스터마이즈하는 방식이어서 최종적으로 사용자가 작성하는 코드도 깔끔하고 부가적인 프로세서가 필요가 없다. shadow-cljs에 아직 그런 부분은 빠져있어서 CSS를 컴파일해서 CLJS파일(또는 JSON파일)을 생성하는 프로세서를 별도로 돌려야 한다.

CSS파일 Incremental 빌드 안됨

안되는건 아니고 아직 구현 안함. 각 CSS 모듈 파일별로 임시 캐시파일을 생성하고, 최종 생성물은 캐시파일을 concat하는 방식으로 하면 훨씬 빨라질 것 같다.

--

--