역자의 말

이 글을 번역한 지 3년 반이 지났다. 지금은 이 방법이 별로라고 생각하게 됐다. 그냥 HTML에 여러 개의 클래스를 쓰자. 그리고 이 글은 @extend를 긍정적으로 묘사하고 있는데, @extend는 사용하지 말자.

즉, 번역을 했으니 남겨두는 것일 뿐, 나는 이 방법을 지지하지 않는다.


* 알아 둘 점 : semantic이라는 단어는 ‘시맨틱’이라고 번역했다. 원래 semantic은 차세대 HTML에서 중요한 개념으로 사용되는 말이다. 번역하면 ‘의미론적’이라는 뜻이 되는데, 사실 ‘의미론적인’ 뭐 이런 식으로 번역해도 큰 문제는 없다. 하지만, 업계에서 더 자주 사용되는 용어는 ‘시맨틱’이다. 그래서 그냥 ‘시맨틱’이라고 번역했다.


객체 지향 CSS는 멋지다. 하지만 시맨틱하지 않은 클래스들로 마크업을 어지럽히는 것은 멋지지 않다. HTML 곳곳에 흩어져 있는 클래스들을 모두 바꿔야 할 일이 생길 텐데, 그건 아주 짜증나는 일이다. 하지만 OOCSS와 Sass를 결합한다면, 두 분야에서 최고의 결과물을 얻게 된다. 모듈화와 HTML 유지보수 용이성. (HTML을 복잡한 CSS로 어지럽힐 필요가 없기 때문에.)

OOCSS는 HTML 유지보수를 힘들게 한다

우선, 빠르게 포기한 사람들, 상당수는 아마 열광했을 것이고, 다음으로 “시맨틱하지 않다”는 이야기를 들었을 것이다. 해법이 있다. 내 진정한 관심사는 단지 시맨틱한지가 아니다. 내 관심사는 유지보수다. 시맨틱하지 않은 클래스는 컴포넌트를 설명할 필요가 없고, 이것은 클래스가 나중에 컴포넌트에서 제거될 수도 있다는 것을 의미한다.

CSS만으로 모듈을 만드는 방법은 시맨틱하지 않은 클래스를 사용하는 방법밖에 없다. (현재로서는.) 그렇다면, 모든 HTML 요소에 이 클래스들을 적용해야 한다. 이것이 OOCSS의 모듈 접근법이다. 하지만 이것은 커다란 문제를 낳는다.

  1. 나는 스타일이 변경될 때마다 HTML 전체를 훑으면서 수정하고 싶지 않다. 나는 스타트업 회사에서 일했는데, 시도 때도 없이 변경이 있었다.
  2. 심지어, 클래스를 추가해야 하는 DOM 요소에 접근 권한이 없는 경우도 있다! 페이지 요소들을 렌더링하는 데 자바스크립트 컴포넌트를 사용하고 있다면, 컴포넌트 안에 있는 요소에는 클래스를 추가할 수가 없다. (뭔가 미친 짓을 하지 않는 이상.)

HTML 유지보수를 힘들게 하는 점만 제외하면, OOCSS의 나머지 부분은 맞는 소리들이다. 반복을 모듈로 추상화하는 것은 대규모 프로젝트에서 CSS를 유지보수하기 용이하게 할 수 있는 유일한 방법이다. 자, 그럼 문제점을 피하면서 이득을 취할 수 있는 방법이 있을까?

구원의 OOSass!   1

OOCSS와 Sass를 결합하면 엄청난 힘이 생긴다. Sass의 @extend 지시자는 다른 선택자의 스타일을 상속할 수 있도록 해 준다. @mixin처럼 모든 것을 중복시키지도 않는다. 하지만 계층적으로 사용하거나, 계층화된 것들과 함께 사용한다면 @extend 호출조차 코드를 지저분하게 만들 수 있다. 2

반갑게도 Sass 3.2플레이스홀더라는 기능을 추가했다. 플레이스홀더는 다른 곳에서 @extend로 호출해 사용하기 전까지는 아무 것도 출력하지 않는 선택자다. 플레이스홀더는 이렇게 생겼다.

%separator
    border-top: 1px solid black

hr
    @extend %separator

.separator
    @extend %separator

위 코드는 이런 CSS를 생성한다.

hr,
.separator {
    border-top: 1px solid black
}

플레이스홀더는 mixin이나 보통의 @extend 호출이 만들어내는 지저분한 코드라는 문제를 피하게 해 준다. 이런 특징 덕분에 플레이스 홀더는 시맨틱하지 않은 CSS 모듈을 만드는 데 최상이다. 나는 이 모듈을 “패턴”이라고 부른다. 패턴에는 다른 데 섞어서 사용할 수 있는 스타일을 조금 넣어 둔다.

실제 사용 사례를 보자

OOCSS 최고의 사례인 .media 모듈을 보자. .media 모듈을 .status, .profile 같은 다양한 컴포넌트에 적용하고 싶을 거다.

하지만, .media 클래스를 모든 HTML에 반복해서 갖다 붙이고 싶지는 않을 것이다. 특히 이미 .status.profile 클래스를 HTML에 널리 사용한 상태라면 말이다. 이럴 때 플레이스홀더를 사용하면 제대로 DRY할 수 있다. 3 이게 우리의 %media 패턴이다.

%media
    overflow: hidden
    &:first-child
        float: left
    &:last-child
        overflow: hidden

이렇게 하면 .media 클래스를 모든 요소들에 반복해서 갖다 붙이는 대신, 원하는 곳에 %media 패턴을 extend하면 된다.

.status
    @extend %media
    // Status-specific styles here...

.profile
    @extend %media
    // Profile-specific styles here...

즉, HTML에 시맨틱한 클래스만 붙여 놓으면 된다는 말이다. .status.profile 같은 것 말이다. 이런 것까지 타이핑하기 싫지는 않을 거다. 이런 클래스들마저 없으면 <article> [같은 HTML] 요소밖에 남지 않을 테니까 말이다.

이렇게 하면 유연성도 얻을 수 있다. status의 스타일을 변경해서 이제 .media 모듈이 필요없다면, @extend 호출을 제거하기만 하면 된다! .media 클래스를 제거하려고 HTML을 돌아다닐 필요가 없다.

눈치가 빠른 사람들은 내가 .media 모듈을 약간 수정해서 사용했다는 걸 알아챘을 거다. 이제, 자바스크립트 컴포넌트를 사용할 때 DOM 접근권이 없는 경우로 돌아가 보자…

OOSass는 자바스크립트 컴포넌트에 스타일을 입히기 쉽게 해 준다

내가 OOCSS를 사용할 때 겪은 가장 큰 문제는, DOM을 완전히 제어할 수 있어서 어디에나 클래스를 붙일 수 있다고 가정한다는 것이었다. 그렇지 않을 때가 있다! 자바스크립트 컴포넌트를 이용해서 (혹은 다른 뭔가를 이용해서) 렌더링할 때, 우리는 단지 컴포넌트 맨 위의 컨테이너만 건드릴 수 있다.

.user-dropdown 요소에 DropdownView를 붙인다고 가정해 보자. .user-dropdown.media 클래스를 붙일 수는 있지만, dropdown의 .button이나 .menu-item 안에 있는 요소에는 클래스를 붙일 방법이 없다. 컴포넌트 안의 DOM은 제어할 수 없기 때문이다.

Sass의 플레이스홀더를 사용하면 문제가 없다.

.dropdown
    // 여기는 모든 dropdown에 적용할 스타일...

.user-dropdown
    // 여기는 특정 dropdowns에만 적용할 스타일...
    .menu-item
        @extend %media

CSS 클래스만 갖고 컴포넌트 안의 DOM을 건드리려면 미친 짓을 해야 한다. 컴포넌트에 들어가서 캡슐화돼 있는 것을 파괴하거나 문자열 기반의 끔찍한 클래스네임 API를 이용하거나. 4 하지만 Sass 패턴을 사용하면 직접 제어하지 않는 DOM 요소가 늘어나도 상관없다. 5

좋아, 좋아, 그럼 예제를 보자

나는 다른 사람들이 만든 CSS 패턴을 자주 읽어 본다. 그래서 나도 내 것을 몇 개 공유하기로 했다. 내가 Segment.io를 만들 때 전체적으로 사용했던 패턴이다.

Lip

이건 애플 스타일의 구분자다. 컨텐츠 위쪽으로 입술처럼 스타일을 만들어 준다. (역방향으로 사용하기 위해서 %reversed-lip도 만들었다.)

[역자 주 - 뭔지 궁금해서 Segment.io에 가 봤는데, 아래 이미지처럼 생긴 놈이다. 섹션을 구분할 때 그냥 선이 아니라 저렇게 은은한 그라데이션으로 만든 거다. 저게 입술처럼 생겼다고 lip이라고 이름붙였나 보다.]

%lip
    clear: both
    display: block
    height: 5px
    background: url('/public/images/patterns/lip/lip.png') no-repeat
    background-size: 100% 100%

%reversed-lip
    @extend %lip
    background-image: url('/public/images/patterns/lip/reversed-lip.png')

Valley

오목해 보이도록 HTML 요소 위아래로 lip을 추가한다.

%valley
    position: relative
    overflow: hidden

    &::before,
    &::after
        content: ''
        position: absolute
        left: 0
        right: 0
    &::before
        @extend %lip
        top: 0
    &::after
        @extend %reversed-lip
        bottom: 0

Plane(평면)

아주 간단한, 둥근 모서리 박스. Segment.io에서 바탕에 색을 입히기 위해 사용한다.

%plane
    box-shadow: 0 2px 5px rgba($black, .1)
    border-radius: $border-radius-medium

%white-plane
    @extend %plane
    background-color: $white

%off-white-plane
    @extend %plane
    background-color: $off-white

...

Seam(경계선)

검은 줄과 하얀 줄을 겹쳐 놔서 반투명하게 보이는 border를 본 적이 있을 것이다. 난 그걸 seam이라고 부른다. (역자 주 : 선 하나 긋는 건데 약간 오목하게 들어가 보이는 선 말이다. 아래 그어져 있는 선이 이 사람이 말하는 선이다. rgba가 지원되는 브라우저에서만 제대로 보인다.)

%seam
    clear: both
    display: block
    height: 0px
    border-top: 1px solid rgba($black, .12)
    border-bottom: 1px solid rgba($white, .15)

Well

valley처럼, 페이지에서 들어가 있는 부분이다. <code> 예제 같은 데서 사용한다. (이 블로그에서 사용하는 것과 비슷하다. [역자 주 - 이 블로그란 저자의 블로그를 말한다.])

seam

%well
    box-shadow: inset 0 1px 5px rgba($black, .14)
    border-radius: $border-radius-medium

%off-white-well
    @extend %well
    background-color: $off-white

%light-gray-well
    @extend %well
    background-color: $light-gray

...

이제 당신의 차례다

이 글을 통해 CSS 컴포넌트에서 패턴으로 할 수 있는 것과 사용법에 대해 잘 알게 됐으면 좋겠다. [패턴을] 사용할 곳은 어디에나 있다.

패턴은 오직 한 가지 기능만 해야 한다. 그리고 그걸 잘 해야 한다. 해리 로버트는 패턴의 이름은 추상적으로, 시맨틱하지 않게 지어야 한다고 논평했다. 그렇게 하면 패턴은 추상화될 것이고, 그럼 어디서나 사용할 수 있게 된다. 또한 패턴은 언제나 다른 것들을 기반으로 작성할 수 있다. 내가 valley 예제에서 한 것처럼 말이다.

비슷한 자기만의 패턴이 있다면, 나에게 한 번 보여 주길 바란다!

Notes:

  1. OOSass to the rescue!
  2. nest를 계층적이라고 번역했다. 원문 필자는 .navi .title 이런 식으로 사용하는 것을 nest라고 칭한 것이고, 그래서 나는 그걸 계층적 사용이라고 번역했다.
  3. DRY는 개발 원칙 중 하나다. Don’t Repeat Youself의 약자.
  4. You’d have to do uncouth things to get that to work with pure CSS classes: reaching into components and destroying their encapsulation, or using some sort of horrific string-based className API.
  5. But with Sass patterns you can easily augment DOM elements that you have no direct control of.