역자: 이 글은 니콜라스 갤러거가 2012년에 쓴 글 About HTML semantics and front-end architecture를 번역1한 것이다. ‘HTML의 시맨틱과 CSS의 시맨틱은 다르다’, ‘CSS 클래스명을 내용과 연관짓지 않는 것이 의미론적 CSS다’ 하는 주장을 담고 있다. 흔히 권장되는 CSS 클래스 작명법과 다른 이 주장은, 오늘날 유지보수하기 쉬운 CSS를 작성하려는 많은 사람들이 받아들이는 주장이다.

혹시나 소개한 글이 있나 해서 찾아 봤는데 없길래 번역한다. 이런 글은 더 많은 사람들이 봐야 한다.


이 글은 내가 좋아하는 생각, 경험, 아이디어들에 관한 것이고, 지난 1년 간 내가 실험한 것들에 관한 것이다. HTML 시맨틱과 컴포넌트를 다룬 뒤 프론트엔드 아키텍처, 클래스 작명 방식, 그리고 HTTP 압축을 다룬다.

우리는 탐험을 멈추지 않을 것이다
그리고 우리 모든 탐험의 끝은
출발한 곳에 도착하는 것이 될 것이다
그리고 그곳을 처음으로 알게 될 것이다.

We shall not cease from exploration
And the end of all our exploring
Will be to arrive where we started
And know the place for the first time.

T.S. 엘리엇 – “리틀 기딩(Little Gidding)”

시맨틱에 대하여

의미론(Semantics)이란 기호(signs)와 상징(symbols) 그리고 그것이 나타내는 것 사이의 관계를 연구하는 것이다. 언어학에서 이것은 주로 언어에서 기호(단어, 문장 혹은 소리 같은 것들)의 의미에 대한 연구다. 프론트엔드 웹 개발자의 맥락에서, 시맨틱은 대개 HTML 요소, 속성, 그리고 속성값(마이크로데이터2 같은 확장을 포함해)에 대해 합의된 의미를 가리킨다. 이 합의된 시맨틱들―보통 공식화된 명세서에 있는―은 프로그램이 (나중에는 사람이) 웹사이트에 있는 정보들(aspects of the information)을 더 잘 이해하도록 돕는 데 사용할 수 있다. 그러나 ― 공식화 이후에도 ― 요소, 속성, 속성값의 의미는 개발자들이 적응시키고 함께 선택할3 대상이다. 이것은 공식적으로 합의된 시맨틱을 개선하는 것으로 우리를 이끌 수 있다. (그리고 이것이 HTML 디자인 원리4다.)

서로 다른 HTML 시맨틱 유형 구분

“시맨틱한 HTML” 작성은 최신의 전문적 프론트엔드 개발에서 기반이 되는 것 중 하나다. 대부분의 시맨틱은 존재하거나 기대하는 콘텐츠의 성격과 연관된다(예컨대, h1 요소, lang 속성, type 속성의 email이란 값, 마이크로데이터).

그러나, 시맨틱한 모든 것이 콘텐츠에서 도출될 필요는 없다. 클래스명은 “시맨틱하지 않을(unsemantic)” 수 없다. 어떤 이름을 사용하든간에 말이다. 클래스명엔 의미가 있고 목적이 있다. 클래스명에서 시맨틱이란 HTML 요소들의 시맨틱과 다를 수 있다. HTML 요소, 특정 HTML 속성, 마이크로데이터 등 합의된 “글로벌한” 시맨틱함을 유지하면서도 class 속성의 값 같은 “로컬” 웹사이트/어플리케이션에 맞는 시맨틱함도 유지할 수 있다. 목적을 헷갈리지 않으면서도 말이다.

HTML5의 명세의 클래스에 관한 절을 보면 여전히 “베스트 프랙티스”라고 간주하는 것을 반복하고 있지만…

… 작성자는 콘텐츠의 성격(nature)을 설명하는 [클래스 속성 - 니콜라스 갤러거] 값을 사용하는 편이 좋다. 콘텐츠 모양이 어떻게 표시되길 바라는가를 설명하는 값을 사용하는 것은 좋지 않다.

… 그렇게 해야 할 본질적 이유는 없다. 사실, 그렇게 하면 대규모 웹사이트나 애플리케이션 작업을 할 때 방해되는 경우가 많다.

  • 콘텐츠 계층의 시맨틱[즉, 내용에서 도출된 것- 역자]은 HTML 요소와 그 밖에 다른 속성들이 이미 담당하고 있다.

  • 클래스명은 기계나 사람에겐 시맨틱한 정보를 거의 또는 전혀 전달하지 않는다. 작은 예외로, 합의된 (그리고 기계가 읽을 수 있는) 이름이 있다. 위에서 언급한 마이크로포맷이다.

  • 클래스명의 주 목적은 CSS와 자바스크립트 훅을 걸기 위한 것이다. 모양이나 동작을 추가할 필요가 없다면 아마 HTML에는 클래스가 필요 없을 것이다.

  • 클래스명은 개발자에게 유용한 정보를 전해 줘야 한다. DOM 조각을 읽을 때 특정 클래스명은 그게 뭘 하는 것인지 알 수 있는 이름이어야 한다. 프론트엔드 개발자만이 아니라 여러 개발자가 HTML을 건드리는 팀인 경우에 특히 그렇다.

간단한 예를 보자.

<div class="news">
    <h2>뉴스</h2>
    [뉴스 콘텐츠]
</div>

news라는 클래스명은 콘텐츠만 보면 이미 명백히 알 수 있는 것 외에 어떤 것도 말해 주지 않는다. 컴포넌트의 아키텍처 구조에 대한 어떤 정보도 전해 주지 않으며, “뉴스”가 아닌 콘텐츠에는 사용할 수도 없다. 클래스명을 콘텐츠 성격에 묶는 시맨틱함은 이미 아키텍처의 확장 능력을 저해하고, 아키텍처를 다른 개발자가 쉽게 사용하기 힘들게 만들었다.

콘텐츠 독립적 클래스명

대안은 디자인에서 구조와 기능이 반복되는 패턴으로부터 클래스명을 도출하는 것이다. 가장 재사용성이 높은 컴포넌트는 콘텐츠 독립적인 클래스명을 붙이는 것이다.

우리는 계층들이 명확하고 명시적으로 연결되게 하는 것을 두려워해선 안 된다. 그것이 융통성 없게 특정 콘텐츠를 반영해 클래스명을 짓는 것보다 낫다. 이렇게 하는 것이 클래스를 “시맨틱하지 않게” 만드는 것이 아니다. 이것은 단지 클래스명의 시맨틱함을 콘텐츠에서 도출하지 않는다는 것을 의미할 뿐이다. 우리는 HTML 요소를 추가하는 것도 두려워해선 안 된다. 그렇게 하는 것이 컴포넌트를 튼튼하고 유연하며 재사용 가능하게 만드는 데 도움이 된다면 말이다. 그렇게 하는 것이 HTML을 “시맨틱하지 않게” 만드는 것이 아니다. 그것은 단지 콘텐츠를 마크업하는 데 필요한 가장 최소한의 요소만 사용하는 것을 넘어선다는 것을 뜻할 뿐이다.

프론트엔드 아키텍처

컴포넌트/템플릿/객체지향 아키텍처의 목표는 한정된 숫자의 재사용 가능한 컴포넌트를 이용해서 서로 다른 콘텐츠 타입을 다룰 수 있게 하는 것이다. 규모 있는(non-trivial) 어플리케이션에서 클래스명을 시맨틱하게 짓는다고 할 때 중요한 것은 실용성을 염두에 두고 지어야 한다는 것이다. 그리고 그것의 주 목적 ― 의미있고, 유연하고, 재사용 가능한 표현/행위에 관한 훅(hook)을 개발자들이 사용할 수 있게 제공하는 것을 최우선으로 해야 한다는 점이다.

재사용 가능하고 결합 가능한 컴포넌트

확장에 용이한 HTML/CSS를 만들려면, 대개는, 클래스를 활용해서 재사용 가능한 컴포넌트를 만들어야 한다. 유연하고 재사용 가능한 컴포넌트는 특정 DOM 요소나 구조5에 의존하면 안 된다. 특정 요소 타입[쉽게 말해, HTML 태그- 역자]을 필수로 해서도 안 된다. 서로 다른 컨테이너에 적용할 수 있어야 하고, 모양을 바꾸는 것도 쉬워야 한다. 컴포넌트를 더 건강하게 만들기 위해, 필요하다면 HTML 요소를 (콘텐츠를 마크업하는 데 필요한 정도를 넘어) 추가로 사용할 수도 있어야 한다. 좋은 예는 니콜 설리반미디어 객체라고 부른 것이다.

컴포넌트를 쉽게 결합하려면 타입 선택자[h1, p 같은 태그 선택자를 말하는 것- 역자]보다는 클래스를 사용해야 한다.6 다음 예는 btn 컴포넌트와 uilist 컴포넌트를 쉽게 결합하지 못하게 만든 예다. 문제는 .btn의 특정도(Specificity)7.uilist보다 낮고 (그래서 겹치는 속성을 덮어 쓰게 될 것이다), uilist 컴포넌트에는 앵커 노드가 자식으로 있어야 한다는 점이다.

.btn { /* 스타일 */ }
.uilist { /* 스타일 */ }
.uilist a { /* 스타일 */ }
<nav class="uilist">
    <a href="#">Home</a>
    <a href="#">About</a>
    <a class="btn" href="#">Login</a>
</nav>

다른 컴포넌트들을 uilist와 결합하기 쉽게 개선하는 방법은 자식 DOM 요소에 스타일을 입힐 때 클래스를 사용하는 것이다. 이 방법은 규칙의 특정도를 감소시켜 주지만, 더 주요한 장점은 어떤 종류의 자식 노드에도 해당 구조의 스타일(structural styles)을 입힐 수 있게 해 준다는 점이다.

.btn { /* 스타일 */ }
.uilist { /* 스타일 */ }
.uilist-item { /* 스타일 */ }
<nav class="uilist">
    <a class="uilist-item" href="#">Home</a>
    <a class="uilist-item" href="#">About</a>
    <span class="uilist-item">
        <a class="btn" href="#">Login</a>
    </span>
</nav>

자바스크립트 전용 클래스

어떤 형태로든 자바스크립트 전용 클래스를 사용하면, 모양이나 구조에 변화가 있을 때 거기에 적용된 자바스크립트가 깨질 위험을 줄여 준다. 내가 발견한 유용한 방법은 자바스크립트 훅_만을_ 위한 특정한 클래스(js-* 같은 것)를 사용하는 것이다. 그리고 거기엔 스타일을 전혀 입히지 않는다.

<a href="/login" class="btn btn-primary js-login"></a>

이 방법을 사용하면 구조나 모양을 변경하다가 우연히 필수적인 자바스크립트 동작이나 복잡한 기능에 영향을 미치는 것을 줄일 수 있다.

컴포넌트 수식어(modifier)

기본 컴포넌트와 모양이 약간 다른 컴포넌트 변종을 사용해야 하는 경우가 자주 있다. 예컨대 배경이나 외곽선 색을 달리하는 경우. 컴포넌트 변종을 만들기 위해서는 주로 두 가지 패턴 중 하나를 사용한다. 여기서는 그것을 “싱글 클래스(single-class)”와 “멀티 클래스(multi-class)” 패턴이라고 부를 것이다.

“싱글 클래스” 패턴

.btn, .btn-primary { /* 버튼 템플릿 스타일 */ }
.btn-primary { /* 주요 버튼과 관련된 스타일 */ }

<button class="btn">Default</button>
<button class="btn-primary">Login</button>

“멀티 클래스” 패턴

.btn { /* 버튼 템플릿 스타일 */ }
.btn-primary { /* 주요 버튼과 관련된 스타일 */ }

<button class="btn">Default</button>
<button class="btn btn-primary">Login</button>

프리 프로세서를 사용한다면, 아마 Sass의 @extend 기능을 이용해서 “싱글 클래스” 패턴 유지보수에 드는 노력을 줄일 수 있을 것이다. 하지만, 프리 프로세서의 도움이 있더라도, 나는 “멀티 클래스” 패턴과 HTML에 클래스 수식어를 추가하는 것을 선호한다.

나는 그게 더 확장성 있는 패턴이라는 점을 발견했다. 예를 들면, 기본 btn 컴포넌트를 만들고, 다섯 종류의 버튼과 세 가지 사이즈를 추가한다고 해 보자. “멀티 클래스” 패턴을 사용하면 결합해 사용할 수 있는 클래스를 9개 만들면 된다. “싱글 클래스” 패턴을 사용하면 클래스를 24개 만들어야 한다.

“멀티 클래스” 패턴을 사용하면, 정말 필요한 경우에, 맥락에 따라 살짝 변형하기도 쉽다. 우리는 다른 컴포넌트 안에 나타나는 모든 btn에 살짝 조정을 가해야 할 지도 모른다.

/* "멀티 클래스" 조정 */
.thing .btn { /* 조정 */ }

/* "싱글 클래스" 조정 */
.thing .btn,
.thing .btn-primary,
.thing .btn-danger,
.thing .btn-etc { /* 조정 */ }

“멀티 클래스” 패턴에서는 컴포넌트 안쪽 선택자 하나만 추가하면 된다. 그러면 이 선택자가 컴포넌트에 안의 btn으로 스타일된 모든 요소를 가리킨다. “싱글 클래스” 패턴에서는 가능한 모든 버튼 종류를 나열해야 할 것이고, 새 변종이 만들어질 때마다 선택자를 조정해야 한다.

구조화된 클래스명

컴포넌트 - 그리고 거기에 입히는 “테마” - 를 만들 때 몇몇 클래스는 컴포넌트 영역 안에서 사용되고, 몇몇은 컴포넌트 수식어로, 또 다른 것들은 DOM 노드들을 묶어 추상적 개념을 나타내는 더 큰 컴포넌트로 만드는 데 사용된다.

btn(컴포넌트)과 btn-primary(수식어), btn-group(컴포넌트), 그리고 btn-group-item(컴포넌트 서브 객체) 사이의 관계를 유추하는 것은 힘든 일이다. 이름이 클래스의 목적을 분명하게 드러내지 않기 때문이다. 일관된 패턴이 없다.

2011년 초에 나는 DOM 조각에 있는 노드들 간의 개념적(presentational) 관계를 더 빨리 파악하게 해 주는 작명 패턴을 실험하기 시작했다. HTML, CSS와 JS 파일을 사이를 오가며 사이트 구조를 파악하려 애쓰는 것을 피할 수 있도록 말이다. 표기법의 핵심은 주로 BEM 시스템의 작명법에서 영향을 받았다. 하지만 내가 훑어 보기 쉬운 형태로 변환한 것이다.

내가 처음 이 글을 쓴 이후, 몇몇 다른 팀들과 프레임워크가 이 접근법을 받아들였다. MontageJS는 표기법을 다른 스타일로 고쳤다. 그것은 내가 현재 선호하는 스타일이 됐고, 나는 그것을 SUIT 프레임워크에서 사용하고 있다.

/* 유틸리티 */
.u-utilityName {}

/* 컴포넌트 */
.ComponentName {}

/* 컴포넌트 수식어 */
.ComponentName--modifierName {}

/* 컴포넌트 자손 */
.ComponentName-descendant {}

/* 컴포넌트 자손 수식어 */
.ComponentName-descendant--modifierName {}

/* 컴포넌트 상태 (컴포넌트 범위에서) */
.ComponentName.is-stateOfComponent {}

이것은 단지 현재 도움이 된다고 보는 작명 패턴이다. 얼마든지 다른 형태가 될 수도 있다. 하지만 오직 (하나의) 하이픈이나 언더스코어, 혹은 카멜 표기법(camel case)8에만 의존하는 것보다 클래스명에서 모호함을 제거한다는 장점이 있다.

원(raw) 파일 사이즈와 HTTP 압축에 대하여

모듈화되고 확장성 있는 CSS에 대한 어떤 토론은 파일 사이즈 “거품(bloat)”에 관한 것이다. 니콜 설리반의 이야기에는 페이스북 같은 회사들이 이 접근법을 통해 (유지보수 용이성을 개선했을 뿐 아니라) 파일 사이즈를 줄였다는 점이 자주 언급된다. 더 나아가, 프리 프로세서의 결과물과 다량의 클래스를 사용한 HTML에 HTTP 압축을 사용해 본 것에 관한 나의 일화를 소개해 보려고 한다.

트위터 부트스트랩이 처음 나왔을 때, 나는 컴파일한 CSS를 내가 선호하는 식으로 재작성해서 파일 사이즈를 비교해 봤다. 두 파일을 최소화(minifying)9했을 때, 손으로 작성한 CSS는 프리 프로세서의 결과물보다 10%쯤 더 작았다. 하지만 두 파일을 gzip으로 압축하자 프리 프로세서의 결과물이 손으로 작성한 것보다 5% 더 작았다.

이것은 HTTP 압축을 적용한 파일 사이즈를 비교하는 것이 얼마나 중요한지 보여 준다. 최소화된(minified) 파일의 사이즈는 전체 그림을 보여 주지 못하기 때문이다. 이것은 프리 프로세서를 사용하는 숙련 CSS 개발자들이 컴파일된 CSS에서 어느 정도의 반복에 대해서는 별로 걱정할 필요가 없다는 것을 보여 준다. HTTP 압축 후에 파일 사이즈가 더 작아지도록 해 주기 때문이다. [또한] 프리 프로세서를 통해 얻을 수 있는, 유지보수 용이한 “CSS” 코드의 이득을 미학이나 CSS 파일 사이즈 ― 원 사이즈든 최소화된 사이즈든 ― 에 관한 걱정보다 우선해야 한다.

또 다른 실험에서, 나는 실서버에서 가져온 60KB짜리 HTML파일에서 모든 클래스 속성을 제거했다(이미 재사용가능한 컴포넌트가 많이 있었다). 이것은 파일 사이즈를 25KB로 만들었다. 원 파일과 클래스를 벗긴 파일을 gzip으로 압축했을 때 두 파일 용량은 각각 7.6KB와 6KB로 1.6KB 차이가 났다. 자유로운(liberal) 클래스 사용으로 인한 파일 용량은 거의 강조할 가치가 없다.

어떻게 안심하는 법을 배우는가…

여러 해 동안, 숙련 개발자들의 경험은 대규모(large-scale) 웹사이트와 어플리케이션 개발 방법의 전환을 이끌어 왔다. 반면, “시맨틱 HTML”이 의미하는, 콘텐츠에서 도출한 클래스명이라는 이데올로기에서는 이제 막 벗어났는데(그조차도, 오직 최수의 수단으로만), 사람들은 그런 접근법의 비현실성을 뼈저리게 느끼기도 전에 대규모 어플리케이션 작업을 하게 된다. 우리는 낡은 아이디어를 거부하고, 대안을 찾고, 심지어 이전에 거부했을 방법조차 재검토할 준비를 해야만 한다.

유지보수할 뿐 아니라 개발도 지속해야 하는(actively iterate) 규모 있는(non-trivial) 웹사이트와 애플리케이션을 작성하기 시작하면, 최선의 노력에도 불구하고 코드가 점점 더 유지보수하기 어려워진다는 사실을 금세 깨닫게 된다. 그럴 때 이런 문제를 해결하기 위해 자기만의 접근법을 제시한 사람들의 작업을 둘러보는 것은 가치가 있다: 니콜 설리반의 블로그와 객체 지향 CSS 프로젝트, 조나단 스눅의 확장성 있는 모듈화된 구조의 CSS(SMACSS), 그리고 얀덱스[러시아의 포털 - 역자]가 개발한 Block Element Modifier(BEM) 방법론.

CSS를 작성하고 편집하는 데 들이는 시간을 줄이는 이런 방법을 시도한다면, 스타일을 변경할 때 요소의 HTML 클래스를 바꾸는 데 더 많은 시간을 소요하게 된다는 점을 받아들여야 한다. 이것은 프론트엔드 개발자든 백엔드 개발자든 ― 미리 제작돼 있는 “레고 블럭”을 재배치할 수는 사람이라면 누구에게나 상당히 실용적이다. CSS 연금술(CSS-alchemy)을 할 줄 아는 사람은 아무도 없다.

  1. 시맨틱은 “의미”, “의미론”, “의미론적”이라고 번역할 수 있겠지만, 거의 고유명사처럼 “시맨틱”으로 사용되므로 “시맨틱” 혹은 “시맨틱한”으로 음차했다. 단, 맥락에 따라서 “의미론”이나 “의미”라고 번역하는 게 더 나은 경우에는 그렇게 했다. 그 외에도 콘텐츠, 프론트엔드 같은 것은 이렇게 처리했다. 단, Element는 요소로, Attribute와 Property는 속성으로, 음차 없이 번역했는데, 이것은 1:1 대응에 가까워 한국어로 했을 때 원래의 뉘앙스가 사라질 염려가 없기 때문이다. 각주는 모두 역자주다.

  2. WHATWG가 제안한 스펙. HTML에 추가적 메타데이터를 달아 검색엔진이나 브라우저가 내용을 더 잘 이해할 수 있도록 돕는 것이 목적이다. 더 자세한 내용은 위키피디아를 참조하면 될 텐데, 한국어로 위키엔 없다. 한글 문서 중엔 파이어준님이 쓴 “Schema.org 이용하여 HTML 데이터 구조화하기”가 이해에 도움이 된다.

  3. “subject to adaptation and co-option by developers”를 번역한 것이다. 직역하면 “개발자들에 의해 적응되고 공동 선택되는 대상” 정도 될 것이다.

  4. “이미 있는 길을 포장도로로 만들라(Pave the Cowpaths)” 항목을 말한다. “작성자들 사이에 사용법이 이미 널리 퍼져 있는 경우, 없애거나 뭔가 새로 만드는 대신 그것을 적용하는 것을 고려하라(When a practice is already widespread among authors, consider adopting it rather than forbidding it or inventing something new.)”

  5. “existing within a certain part of the DOM tree”를 번역한 것이다. 직역하면 “DOM 트리의 특정 부분에 존재하는 것”

  6. Components that can be easily combined benefit from the avoidance of type selectors in favour of classes. 직역 불능에 가까워 의역했다.

  7. Specificity란 무엇인가? 두 개의 선택자가 같은 요소를 가리키고 있고, 서로 다른 색을 지정했다고 해 보자. 그러면 브라우저는 두 선택자 중 어디에 속한 CSS 규칙을 적용할지 가려야 한다. 그 때 두 선택자 중 Specificity가 높은 선택자에 속한 규칙을 택하게 된다. 예컨대 id가 클래스보다 Specificity가 높고, 클래스 두 개가 중첩된 것이 클래스 한 개만 사용한 것보다 Specificity가 높다. 번역을 뭘로 할까 고민을 좀 했는데, 나는 “특정도”로 번역했다. MDN에서는 “명시도”로 번역했더라. MDN의 “명시도”라는 번역은 사전적 의미로도 크게 무리는 없지만 “더 잘 보인다” 하는 식의 뉘앙스가 느껴진다. “특정도”라는 번역은 “이것보다 저것을 더 골랐다” 하는 뉘앙스를 담은 번역이다. Specificity란 단어의 영어사전 뜻도 비슷한데, 옥스퍼드는 “특별함, 우수함”, 동아출판과 YBM은 “특수성, 전문성”으로 적고 있다.

  8. “카멜표기법 camelCase, 파스칼표기법 PascalCase” 참고

  9. 공백을 없애고 한 줄로 만드는 등, 불필요한 문자를 지워서 최소화하는 기법을 말한다.

- 댓글 기능은 없습니다. 댓글 대신 mail@mytory.net으로 메일 보내 주세요.