일반 CSS의 문제점
CSS는 DOM 요소의 스타일을 지정한다. 보통 하나의 웹 애플리케이션을 만들 때 두 개 이상의 자바스크립트 모듈 파일로 프로젝트를 쪼개서 관리하며, 파일이 쪼개지는 방식에 따라 담당하는 UI 영역의 CSS 파일도 분리하는 게 일반적이다.
자바스크립트는 ES6부터 .js
를 모듈로 분리해 내는 것이 언어의 표준으로 채택됐다. 그에 따라 모듈을 분리한 자바스크립트 파일은 네임 스페이스를 공유하지 않게 되었고, 변수나 함수의 이름을 지정할 때 전역 네임 스페이스만을 따를 때보다 조금 더 자유가 생겼다(다른 모듈의 변수-함수명을 신경 쓸 필요가 없어졌다). 하지만 CSS는 전역으로 선택자 이름이 공유되면서, 컴포넌트 기반 UI 개발을 할 때 걸림돌이 되었다.
/* Home 컴포넌트(Home.css) */
div {
background-color: red;
}
/* Button 컴포넌트(Button.css) */
/* Home에 있는 div 스타일을 덮어쓸 것으로 예상됨 */
div {
background-color: blue;
}
위 예시에서 Home.css
와 Button.css
는 파일이 분리되어 있지만, 자바스크립트 모듈처럼 파일 분리만으로 모듈 시스템이 적용되진 않는다. 따라서, Home.css
와 Button.css
의 네임 스페이스는 공유되고, 만약에 Home
컴포넌트 내부에 Button
컴포넌트가 위치한다면 파일이 호출되는 순서에 따라 나중에 호출된 CSS 파일의 div
스타일이 이전 스타일을 덮어쓸거다.
규모가 작은 서비스에선 CSS 선택자(id
, class
등)를 잘 지정해서 문제를 피해 갈 수 있겠지만, 프로젝트의 규모가 커질수록 네임 스페이스가 오염되는 문제는 더 자주, 심각하게 발생할 것이다. 이런 문제를 해결하기 위한 다양한 시도들이 있었고, 그중 하나가 CSS 모듈(Module)이다.
CSS 모듈이란?
CSS 모듈은 자바스크립트가 파일을 구분했을 때 모듈로써 동작하는 것처럼, CSS도 구분된 파일을 모듈로 인식하게 하는 기능이다. CSS 모듈은 정식 표준으로 등록된 게 아니기 때문에, 작성된 CSS 파일을 파싱 해줄 별도의 번들러 사용이 필요하다. 바꿔 말하면, 번들러 도구 없이 진행되는 일반 바닐라 자바스크립트 프로젝트에서는 CSS 모듈을 사용할 수 없다.
CSS 모듈은 파일 이름을 모듈명.module.css
형식으로 지어서 사용할 수 있다. 보통 리액트나 Vue.js 등 프레임워크에서 모듈 CSS를 지원하며, 실제 코드가 실행될 때 .module.css
라는 확장자가 붙은 파일은 별도로 파싱해 자바스크립트 코드로 변환하는 과정을 거친다.
모듈 CSS를 사용하면 분리된 CSS 파일마다 서로 다른 네임 스페이스를 가질 수 있도록 파싱 시 자바스크립트로 처리를 해준다. 단, 해당 처리가 자바스크립트 모듈 시스템처럼 파일 분리만으로 완벽하게 독립적인 네임 스페이스를 갖도록 만드는 것은 아니며, 모듈마다 고유한 해싱 알고리즘이 적용되어 모듈끼리 충돌이 없는 클래스명을 갖도록 해줄 뿐이다.
즉, CSS 모듈은 각 파일의 클래스명을 고유화하여 모듈 간 충돌을 방지하지만, 전역적으로 선언된 스타일(예: :global
)이나 CSS의 상속 특성은 여전히 영향을 줄 수 있음을 주의해야 한다.
CSS 모듈 사용 방법
위에 언급했듯이, 모듈 CSS 파일은 .module.css
로 확장자를 작성해줘야 한다. 해당 확장자로 정의된 파일은 번들링 시 별도 파싱이 이루어진다.
해당 파일을 리액트 컴포넌트에선 import styles from './모듈명.module.css'
형태로 불러온다. 여기서 styles
는 모듈 CSS 파일에 정의된 선택자들과 스타일 정보가 자바스크립트 객체로 변환된 결과를 나타낸다. 즉, styles
라는 식별자 안에 모듈명.module.css
라는 CSS 모듈에 정의된 모든 스타일에 대한 정보가 담기게 되는 것이다(styles
는 관례에 의해 임의로 지정한 스타일 객체 이름이고, 원한다면 다른 이름으로 지정해도 된다).
이렇게 모듈 CSS 파일을 import
해온 후, 실제 스타일을 적용하고 싶은 부분엔 styles.{선택자}
형태로 클래스를 지정해 준다. 아래는 CSS 모듈 파일을 정의하고, 실제 스타일링을 적용해 본 예시 코드이다.
/* index.module.css */
.container {
background-color: red;
color: white;
}
/* styles라는 객체에 스타일 정보가 저장됨 */
import styles form './index.module.css/'
const App = () => {
return (
<div className={styles.container}>
Hello World
</div>
)
}
// ... App 컴포넌트 렌더링
위의 예시를 살펴보면, index.module.css
라는 모듈의 .container
라는 선택자에 정의한 스타일에 객체 점 표기법 형태로 접근해주고 있다. 해당 예시에서 styles
객체는 대략 아래와 같은 형태를 가질 것으로 예상된다.
const styles = {
container: {
'background-color': red;
color: white;
}
}
이렇게 클래스 명을 입력해 주면, 실제 렌더링된 화면을 확인해 보면 div.container
요소에는 div._container_1gi26_1
와 같은 클래스명이 정의되어 있는 것이 확인된다.
CSS 모듈은 클래스 이름이 다른 파일에서 동일하게 사용되더라도 충돌하지 않도록 파일의 경로와 파일 이름을 고려하여 해시를 생성해 정의한 클래스 명에 추가한다. 예를 들어, src/components/Button/index.module.css
파일과 src/components/Card/index.module.css
파일의 .button
클래스는 경로가 서로 다르기 때문에 서로 다른 클래스 명을 갖게 되는 것이다.
CSS 모듈 파일 경로 외에도 정의된 클래스 이름, 프로젝트 환경 및 복잡한 해시 알고리즘을 통해 고유한 해시값을 생성하며, 이를 클래스 이름 뒤에 붙여주는 식으로 모듈마다 클래스 이름의 중복을 방지해 주는 것이다.
모듈 CSS의 styles
는 자바스크립트의 객체로 변환되기 때문에, 선택자 이름 정의 및 사용 시 자바스크립트 객체의 프로퍼티 이름의 작명 규칙을 신경 써줘야 한다. 예를 들어 숫자로 시작하거나 -
, _
등을 제외한 일부 특수 문자가 포함되면 안 된다거나, 선택자 이름에 -
이 포함될 경우 요소의 className
을 줄 때 괄호 표기법을 사용해줘야 하는 등, 이름 작성에 신경을 써줘야 한다(자바스크립트 객체로 변환되는 것을 전제해서, 모듈 CSS의 클래스명에는 일반적으론 카멜 케이스를 사용한다고 한다).
/* index.module.css */
.div-container {
background-color: red;
color: white;
}
import styles form './index.module.css/'
const App = () => {
return (
// - 가 포함된 선택자 이름은 괄호 표기법으로 접근
<div className={styles['div-container']}>
Hello World
</div>
)
}
// ... App 컴포넌트 렌더링
만약에 두 개 이상의 선택자를 클래스를 사용해줘야 한다면 템플릿 리터럴 문자열을 사용해 나열해 줄 수 있다.
import styles form './index.module.css/'
const App = () => {
return (
// - 가 포함된 선택자 이름은 괄호 표기법으로 접근
<div className={`${styles.container} ${styles.innerContainer}`}>
Hello World
</div>
)
}
// ... App 컴포넌트 렌더링
만약에 템플릿 리터럴을 사용하고 싶지 않다면, 원하는 CSS 클래스 이름을 배열로 만든 다음 [styles.container, styles.innerContainer].join(' ')
형태로 사용해 주는 것도 가능하다.
모듈 CSS의 장단점
모듈 CSS는 사용 방법이 간단하며, CSS 모듈 간 클래스 이름 중복 문제를 손쉽게 해결할 수 있다는 큰 장점이 있다. 프론트엔드 개발에서 크게 유행하고 있는 리액트라는 라이브러리가 컴포넌트 기반 UI 개발을 한다는 것을 고려했을 때, 컴포넌트마다 CSS 모듈을 생성하여 해당 컴포넌트 고유의 네임 스페이스를 만들어 스타일을 관리하는 방식은 꽤 합이 좋은 조합이라고 할 수 있다.
하지만, 모듈을 작게 쪼개서 CSS를 정의하기 때문에 프로젝트가 커지면 CSS 파일이 많아진다는 단점이 있다(이 부분은 CSS 모듈을 나누는 규모? 단위?를 합리적인 수준으로 좁히는 결정을 한다면 꽤 상쇄가 가능한 단점인 것 같긴 하다).
사실, 내가 생각한 가장 아쉬운 점은 CSS가 파싱 되어 해싱된 값과 연결된 클래스 이름으로 생성되었을 때의 가독성이다. 경우에 따라선 클래스 이름을 길게 나열해서 쓰기도 하고, 조건부로 상황에 따라 원하는 클래스 이름을 줘서 다른 스타일을 먹이게 하고 싶을 수도 있다. 이런 상황에서 개발을 할 땐 분명 실제 스타일이 잘 적용되었는지, 잘 적용되지 않았다면 어떤 클래스 이름 부분에서 오류가 났는지를 코드와 비교해면서 계속 찾아야 하는 상황이 존재할 거다. 그런 상황에서 지저분한 해시 값이 붙은 HTML 요소의 클래스 명을 보고 직관적으로 문제를 파악하거나, 코드와의 차이를 비교하는 게 쉽지 않을 수 있다는 문제가 있다.
그 이외에도, CSS 모듈이 작성된 코드 자체의 가독성이 나빠져서, 대안으로 className
이라는 라이브러리를 모듈 CSS와 함께 많이 사용한다는 자료를 여러 곳에서 봤다. 그런데, 아직은 내가 직접 className
을 써본 게 아니고, 여러 예시 코드들을 봤을 때 className
을 쓴다고 월등히 가독성이 좋아진다는 느낌을 받진 못하겠어서, 이 부분은 직접 사용해 보면서 느껴봐야 할 것 같다. 만약에 className
이라는 것의 필요성을 직접 느끼게 된다면 그 시점에 추가적으로 공부해 보고 포스팅도 남겨봐야겠다.
결론
농담 반 진담 반으로, 프로그래머가 가장 힘들어하는 일이 이름 짓기라는 밈을 많이 봤었다. 개발 공부하기 전에도 '정말 그렇겠다'라고 생각했는데, 실제로 공부를 하다 보니 진짜 너무 괴로운 일이 맞다는 생각이 든다. GPT의 도움으로 그래도 괜찮은 이름을 잘 지으면서 넘기고 있지만, 네임 스페이스가 너무 넓어서 오는 충돌과, 그 상황에서 어떤 이름을 어떻게 바꿔야 할지 결정하는 건 정말 너무 괴로운 일이다. 그런 의미에서, 모듈 CSS처럼 이름 중복 문제를 해결하기 위한 대안은 적극적으로 사용해 보는 게 좋은 것 같다.
일단은 모듈 CSS를 먼저 사용해 보지만, CSS의 여러 불편함을 개선하기 위한 다양한 방법론이 있는 걸로 알고 있다. 하나씩 잘 경험해 보면서 어떤 상황에서 어떤 방식을 적용하는 게 좋은지 나만의 기준을 잘 세워봐야겠다. 끝.
- 레퍼런스
'HTML-CSS' 카테고리의 다른 글
input 태그로 Date Picker 구현하기 (1) | 2024.12.04 |
---|---|
사용자 지정 CSS 속성(CSS 변수) 사용하기 (0) | 2024.12.02 |
HTML label 태그 사용하기 (1) | 2024.11.17 |
Form 태그의 유용한 기능들 (0) | 2024.11.10 |
반응형 웹과 미디어 쿼리(Media Query) (4) | 2024.11.04 |