패키지 매니저 pnpm, 새로운 의존성 관리 방식(vs npm/yarn)

기존의 호이스팅 방식과 pnpm의 의존성 트리 유지 방식

pnpm은 Performant NPM, 즉 높은 성능을 내는 npm이라는 의미이다.

npm은 공식적으로 ‘줄여쓴 약자가 아니다(npm is not an acronym)’라고 하지만 누구에게나 Node Package Manager라고 하면 쉽게 설명이 된다…

회사 프로젝트에서도 npm yarn yarn berry를 거쳐 현재는 pnpm을 기본으로 사용하고 있으며, 특성상 낮은 노드 버전 유지가 필요한 곳만 npm을 사용하고 있다.

그렇다면 다른 패키지 매니저와 비교해 pnpm의 특징을 먼저 살펴보자.

특징pnpmnpm / yarn
속도빠름(병렬 처리)다소 느림
효율성좋음(글로벌 캐싱, 링크 사용)낮음(중복 저장 가능성)
호환성npm 호환Node.js 기본
의존성 관리엄격함(충돌 방지 장점)느슨함
모노레포 지원워크스페이스 지원yarn 일부 지원
pnpm과 npm/yarn의 차이

pnpm의 의존성 관리 방식은 기존의 호이스팅(hoisting) 방식을 사용하는 패키지 매니저와 차별화된다. 호이스팅 방식과 pnpm 고유의 의존성 트리 유지 방식을 비교해보자.

호이스팅 방식

전통적인 패키지 매니저(npm, yarn)에서 사용되는 방식으로, 의존성을 프로젝트 루트의 node_modules 디렉토리로 끌어올리는 것을 의미한다.
모든 의존성을 루트 디렉토리에 설치하여, 하위 패키지에서 해당 의존성을 마치 루트에 설치된 것처럼 접근이 가능하도록 한다.

호이스팅 방식의 장점

1. 간단한 접근 : 모든 패키지가 같은 디렉토리에 설치되므로, 의존성을 찾는 데 간단한 구조를 갖는다.

2. 중복 제거 : 중복된 의존성을 상위로 끌어올려 디스크 사용량이 줄어든다.

호이스팅 방식의 단점

1. 의존성 충돌 : 서로 다른 패키지가 동일한 이름의 의존성을 서로 다른 버전으로 요구할 때, 하나의 버전만 설치되므로 충돌 발생 가능성 존재한다.

2. 의존성 숨김 문제 : 하위 패키지가 암묵적으로 루트에 있는 의존성에 접근하게 되어, 의존성을 명시하지 않고 사용할 위험이 있다.

따라서 패키지가 다른 프로젝트에서 독립적으로 작동하지 않을 가능성이 높아진다.

pnpm 고유의 의존성 트리 유지 방식

pnpm은 의존성을 독립적으로 관리하며, 각 패키지의 의존성 트리를 유지한다. 또한 “flat” 구조 대신 하드 링크와 심볼릭 링크를 사용하여, 의존성이 설치된 위치를 참조만 한다.

작동 방식

.pnpm 디렉토리

모든 의존성은 루트의 node_modules 디렉토리 내부 .pnpm 디렉토리에 설치된다. 중복된 의존성은 단일 디렉토리에 저장하고, 필요한 위치에서 해당 파일에 심볼릭 링크를 생성한다.

독립적 의존성 트리

각 패키지는 자신만의 node_modules를 가지고, 필요한 의존성을 .pnpm에서 참조한다.

예를 들면 다음과 같은 구조다.

project/
├── node_modules/
│   ├── package-a/
│   │   └── node_modules/
│   │       └── dependency-a@1.0.0 -> .pnpm/dependency-a@1.0.0
│   ├── package-b/
│   │   └── node_modules/
│   │       └── dependency-a@2.0.0 -> .pnpm/dependency-a@2.0.0

따라서 package-a와 package-b는 서로 다른 버전의 dependency-a를 독립적으로 사용할 수 있다.

pnpm 방식의 장점

1. 의존성 충돌 방지 : 각 패키지가 독립적으로 의존성을 관리하므로, 서로 다른 버전의 의존성을 문제없이 사용할 수 있다.

2. 빠른 설치 : 동일한 의존성을 중복 다운로드하지 않고, 심볼릭 링크를 통해 참조하므로 속도가 빠르다.

3. 디스크 공간 절약 : .pnpm 디렉토리에 모든 의존성을 공유 저장하므로 중복 설치를 방지한다.

4. 명시적 의존성 사용 : 패키지는 반드시 자신이 명시적으로 선언한 의존성만 접근이 가능하며, 루트에 설치된 의존성에 암묵적으로 의존하지 않는다.

pnpm 방식의 단점

1. 복잡한 디렉토리 구조 : 심볼릭 링크를 사용하여 디렉토리 구조가 일반적인 방식보다 복잡해 보일 수 있다.

2. 호환성 문제 : 일부 오래된 라이브러리나 스크립트가 node_modules 트리 구조를 가정한 방식으로 동작할 경우 문제가 생길 수 있다.


pnpm의 의존성 관리 방식은 의존성 충돌 방지, 디스크 공간 절약, 명시적 의존성 관리에 강점을 가지고 있다. 특히 대규모 프로젝트에서 각 패키지의 독립적 의존성을 관리에 매우 유용하다.

참고
https://docs.npmjs.com
https://pnpm.io

렌더링 엔진과 특징 분석하기(webkit, blink, gecko)

독립 발전하는 웹 브라우저 렌더링 엔진

웹 브라우저의 대표적인 렌더링 엔진은 Webkit, Blink, Gecko 세 가지가 있다.

각각의 발전사와 점유율, 특징을 살펴보자.

1. 엔진의 점유율

각 엔진의 점유율은 해당 엔진을 사용하는 브라우저의 점유율 추정치로 살펴보면 다음과 같다.

엔진BlinkWebkitGecko
점유율75%20%3%
브라우저Google Chrome(65%)
Microsoft Edge(5%)
Opera(2.5%)
Samsung Internet(2.5%)
Brabe
Vivaldi
Safari(19%)
iOS용 모든 브라우저
Mozilla Firefox
Statcounter Global Stats – Browser Market Share 참고

‘iOS용 모든 브라우저’는 애플 OS에서 사용되는 크롬, 엣지 등은 Webkit, 이외의 윈도우, 안드로이드 등의 OS에서 사용되는 크롬, 엣지는 Blink를 사용한다는 의미가 된다.

따라서 애플 기기의 브라우저는 모두 Webkit을 사용하며, 이는 전체에서 대략 20%가 된다.

이 같은 특성을 봤을 때 맥 OS에서 개발하고 크롬의 호환성 OK 여부를 결정할 것이 아니라 실제 OS를 변경하여 브라우저 테스트의 필요성이 제기된다.

2. 발전사

Webkit

Webkit은 애플에서 개발한 오픈소스 렌더링 엔진으로 KHTML(1998년부터 사용된 HTML 렌더링 엔진)과 KJS(JavaScript 엔진)을 기반으로 2002년부터 개발되었다.

웹 브라우저에서 웹 페이지를 렌더링하기 위해 사용되며, HTML, CSS, JavaScript 등을 해석하여 사용자가 볼 수 있는 형태로 화면에 렌더링한다.

2003년 Safari 브라우저 출시와 함께 Webkit을 공식적으로 발표했으며, 2005년 오픈소스화하여 현재까지 애플 생태계 전반에 걸쳐 기본 렌더링 엔진으로 사용된다.

Webkit의 구성 요소는 다음과 같다.

WebCore – HTML, CSS, SVG, 이미지 등의 렌더링과 관련된 부분을 처리하는 핵심 렌더링 엔진

JavaScriptCore(Nitro 엔진) – JavaScript 코드를 해석하고 실행하는 엔진으로 성능이 개선된 Nitro 엔진이 주로 사용됨. Webkit을 사용하는 브라우저는 기본적으로 별도의 JavaScript 엔진이 필요없음

Web Inspector – 내장 디버깅 도구로 DOM, css, 네트워크 ㅇㅎ청 등의 분석과 디버깅이 가능

플랫폼 특정 코어 – 운영 체제와 엔진을 연결하거나 플랫폼 간의 차이를 추상화, 특정 플랫폼에서의 성능 최적화 등을 담당

Webkit의 특징으로는 웹 표준을 준수하여 최신 웹 표준을 지원하며 지속적인 최적화를 통해 성능과 에너지 효율성이 강화되었다. 특히 모바일 기기에서 배터리 소모 최소화에 중점을 둔다.

초기부터 경량화된 구조와 빠른 렌더링 속도가 장점이며 높은 에너지 효율성과 함께 애플 생태계에 최적화된 성능과 안정성을 보여준다.

그러나 애플 중심 개발과 강한 통제로 인해 애플 생태계 외에서는 기술 채택이 더디며 타플랫폼의 기여가 제한적이고 점유율이 낮다.

Blink

2013년 구글이 Webkit을 fork하여 만든 렌더링 엔진이다. Chrome 브라우저도 초기에는 Webkit을 사용했지만 아키텍쳐와 개발 방식이 맞지 않아 Blink를 개발하게 되었다.

초기 버전은 상당히 많은 부분의 코드를 공유했지만 독자적인 개발 노선을 걷기 시작하고 멀티 프로세스 아키텍쳐에 집중하여 다양한 운영 체제와 디바이스에서 원활히 작동하도록 설계되었다.

Chromium(구글 주도 오픈소스 웹 브라우저 프로젝트)은 Blink 엔진을 사용하므로 Chromium을 기반으로 하는 브라우저는 모두 Blink 렌더링 엔진을 사용한다.

Blink의 주요 구성 요소는 다음과 같다.

DOM core – HTML 파싱과 DOM 트리 생성 및 조작 지원

Layout&Render Engine – 레이아웃 계산 및 렌더 트리 생성

CSS Parser&Style Engine – CSS 파싱 및 스타일 계산

DevTools – 개발자용 디버깅 및 성능 분석 도구

참고로 Blink는 내부에 JavaScript 엔진을 포함하지 않고 외부 엔진인 V8 엔진을 사용한다.

특징으로는 멀티 프로세스 모델과 통합되어 각 탭이 독립적으로 실행되므로 보안이 강화된다. 렌더링 성능 향상을 위해 GPU를 적극 사용하고 멀티 프로세스 구조로 빠른 성능과 광범위한 생태계가 장점이다.

시장 점유율이 집중화되면서 경쟁이 줄고 웹 기술 발전이 특정 기업에 의존 경향을 보이는 것과 메모리, CPU 사용량이 많아 리소스 소모가 크고 해당 생태계에 속한 브라우저의 독립성이 감소하는 단점이 있다.

Gecko

1997년 Netscape에서 개발을 시작하였으며, 2004년 Firefox의 핵심 엔진으로 채택되었다. 2017년 Quantum 프로젝트를 통해 멀티스레드 지원, GPU 활용, 웹 표준 강화 등을 강화했다.

주요 구성 요소로는 HTML parser, CSS parser, Rendering Engine 등이 있으며, Gecko에 통합된 JavaScript 엔진은 SpiderMonkey를 사용하며 개발자 도구인 Developer Tools도 제공한다.

특징으로는 Servo 프로젝트 기술 통합을 통해 Stylo CSS 엔진을 도입하고 멀티스레드 렌더링을 지원하며 GPU 가속 활용이 강화되었다. 강력한 개인정보 보호 및 확장성이 장점이지만 낮은 시장 점유율과 Blink보다 리소스 사용량이 많은 것이 단점이다.


출처
Chromium Documentation
Google Developers Blog
Webkit Official
Mozilla Developer Network(MDN)

맥 nvm 14 버전 설치 오류 해결(nvm install 14)

apple silicon,nvm node 버전 14 설치하기

비교적 오래된 프로젝트에서는 호환성 문제로 인해 노드 버전을 낮춰서 작업하는 상황이 발생한다.

이 때 NVM을 사용하면 편리하게 노드 버전을 변경할 수 있다.

하지만 특정 버전(특히 node 14)은 설치가 되지 않고 에러 메시지가 끝없이 올라가는 상황이 발생한다.

이 때는 아키텍쳐(맥 실리콘, M1, M2, M3 등)이 원인인 상황이 많으므로 로제타2(Rosetta2)를 사용해 아키텍쳐를 x86_64로 전환해야 한다.

먼저 로제타2가 설치되어 있는지 확인하기 위해 다음 커맨드를 입력한다.

$ /usr/bin/pgrep oahd

프로세스 ID가 반환되면 이미 설치되어 있는 상태이다.

설치되어 있지 않다면 먼저 로제타2를 설치한다.

로제타2 설치 방법 (애플 서포트 페이지)

이제 터미널에서 다음 커맨드를 입력해보자.

$ arch

arm64가 출력되면 다음 커맨드를 사용해 x86_64로 전환한다.

arch -x86_64 zsh

다시 arch 커맨드를 입력해보면 변환된 아키텍쳐가 반환되는 것을 확인할 수 있다.

이제 nvm install 14를 문제없이 설치할 수 있다.

페이지 이동 후 쿠키가 사라지는 문제(Next.js, setHeader, writeHead)

설정한 쿠키가 페이지 이동 후 사라질 때 의심해 볼 상황

getServerSideProps에서 writeHead 또는 setHeader로 쿠키를 설정하고 redirect 시

등록된 쿠키가 사라지는 상황이 있다.

redirect된 페이지에서 쿠키가 사라진다면 헤더 설정의 문제를 확인해 봐야 한다.

합리적으로 의심해 볼 수 있는 문제는 페이지 이동 후 쿠키가 사라졌으므로 쿠키의 사용 범위에 대한 설정 문제이다.

Path=/ 설정은 쿠키가 모든 경로에서 유효하도록 설정하는 옵션이다. 이 옵션을 통해 모든 페이지에서 쿠키에 접근할 수 있도록 설정하므로 해당 옵션이 누락되면 페이지 이동 시 쿠키가 사라지는 문제가 발생한다.

setHeader와 writeHead를 사용해 path=/를 포함하는 쿠키 설정 예제를 확인해보자.
HttpOnly는 클라이언트의 자바스크립트에서 쿠키에 접근할 수 없도록 설정하는 옵션이다.

export async function getServerSideProps({ req, res }) {

  res.setHeader('Set-Cookie', 'myCookie=value; Path=/; HttpOnly; Max-Age=3600');

  return {
    redirect:{
      destination: '/new-page', // redirect할 페이지
      permanent: false
    }
  }
}

writeHead의 사용도 확인해보자.

export async function getServerSideProps({ req, res }) {
  const cookieVal = 'myCookie=value; Path=/; HttpOnly; Max-Age=3600';

  res.writeHead(302, {
    Location: 'new-page', // redirect할 페이지
    'Set-Cookie': cookieVal,
    'Custom-Header': 'Custom', // 커스텀 헤더
  });
  
  res.end();

  return {
    props: {}
  };
}

writeHead의 첫 번째 파라미터는 HTTP 응답 상태 코드, 두 번째는 헤더 객체를 설정한다.

헤더 객체 내부의 Location 필드로 redirect 페이지를 설정할 수도 있고, return 문에 redirect를 전달하면서 페이지를 설정할 수도 있다.

Next.js의 기본 권장 방식은 return 문에서 redirect를 전달하는 방법이다.

그렇다면 writeHead와 setHeader의 차이점은 무엇일까?

  • writeHead는 상태 코드를 설정(setHeader는 불가능)할 수 있는 등의 세세한 설정을 할 수 있다.
  • setHeader는 응답이 시작(res.end())되기 전 여러 번 호출이 가능하지만 writeHead는 한번만 호출한다.

따라서 상태 코드와 여러 헤더를 동시에 설정하려면 writeHead, 개별적으로 헤더를 추가하고 수정할 때는 setHeader를 사용하는 것이 좋다.

이와 같이 Path=/를 추가하면 이동하는 페이지에 상관없이 애플리케이션에서 쿠키를 확인할 수 있다.


추가 참고 : Next.js 공식 문서(Setting Headers)

다양한 이미지 파일 종류 알아보기(WEBP, PNG, SVG, JPG, TIFF….)

Webp, png, svg, gif, psd, eps, tiff

이미지 파일 종류는 크게 웹용(WEBP, PNG, SVG, JPG, GIF)과 인쇄용(PSD, EPS, TIFF)으로 나눌 수 있다.

1. 웹용 이미지 데이터

– SVG
벡터(Vector) 그래픽을 기반으로 하는 이미지 데이터. 크기를 조절해도 품질이 저하되지 않으며 텍스트 기반이므로 코드로 이미지를 수정할 수 있다. 로고, 아이콘, 일러스트 등에 적합하며 PNG보다 더 작은 용량을 유지할 수 있다.

– PNG
무손실 압축 중 작은 파일 용량이 장점인 래스터(Raster) 형식의 이미지 데이터. 그림이나 로고 등 간단한 이미지에 적합하며 SVG에 비해 브라우저 호환성, 투명 배경, 필터 효과, 복잡한 색상과 세부 사항 표현 등에 더 유용하다.
무손실 압축이지만 해상도 제한으로 인해 크기에 따라 품질이 저하될 수 있다.

– WEBP
구글이 개발한 이미지 형식으로 손실과 무손실 압축을 모두 지원한다. 손실은 JPG, 무손실은 PNG와 유사한 방식으로 품질을 유지하고 파일의 크기를 줄인다. 알파 채널을 지원하여 투명 배경을 사용할 수 있고 여러 장의 사진을 사용해 애니메이션을 만들 수 있다. 동일 품질 대비 JPG, PNG보다 약 30% 가량 크기가 작으며, 웹 이미지 최적화 등에 사용된다.

– JPG
JPEG라고도 하며, 사진이나 그라데이션 등 여러 색상을 포함하는 이미지에 적절하다. 손실 압축을 사용하므로 용량이 작지만 같은 이미지를 계속 JPG로 덮어쓰게 되면 계속되는 손실 압축으로 인해 화질 저하가 계속 발생하게 된다.

– GIF
색의 수가 256색으로 파일 용량이 작으며, 여러 이미지의 배열을 통해 움직이는 영상을 만들 수 있다. 무손실 압축 방식을 사용하지만 색상 수의 제한으로 인해 일부 사항이 손실될 수 있다.

2. 인쇄용 데이터

– TIFF
이미지를 압축하지 않고 저장하는 형식의 데이터. 인쇄 및 그래픽 작업에서 인기가 많으며 저장을 반복해도 화질 저하가 발생하지 않는다. 무손실 방식이므로 파일 크기가 크고 압축, 최적화가 부족하여 웹용으로는 적절하지 않으며, 대부분의 브라우저에서 제대로 지원되지 않는다.

– PSD
포토샵 저장 형식으로 레이어 정보를 포함해 저장할 수 있다. 레이어 정보를 포함하면 용량이 커지므로 저장 시 레이어 병합을 통해 용량을 줄일 수 있다. 포토샵 기능을 활용해 복잡한 이미지를 만들 수 있으며, 고해상도 이미지를 저장할 수 있다. 브라우저가 지원하지 않으므로 JPG, PNG등으로 변환해야 한다.

– EPS
어도비 PortScript로 만든 형식의 데이터. 저해상도와 고해상도를 가지고 있어 저해상도로 작업하고 고해상도로 출력할 수 있다. 벡터 그래픽 장점을 최대한 활용할 수 있으며, 주로 인쇄에 적합하다.


MDN – SVG
Wikipedia – PNG
Wikipedia – WebP

spec.template: Invalid value 에러


~~~ is invalid. spec.template: Invalid value

yaml을 작성하고 kubectl apply -f sample.yaml과 같이 적용할 때 spec.template에러가 뜨는 상황이 있다.

이 에러가 뜬다면 해당 yaml로 파드를 처음 생성하는 것이 아니며 ,수정 사항이 조건에 위배되기 때문이다.

spec.Completions
spec.Selector
spec.Template
 

이 항목들은 수정이 불가하도록 지정되어 있다. 따라서 spec.template를 수정하는 apply하는 상황에서 발생한 에러이다.

수정이 불가하므로 신규 생성만 가능하다.

간단한 해결 방법으로 해당 yaml을 kubectl delete -f sample.yaml로 제거한 뒤 다시 apply를 적용하면 에러가 해결되는 것을 확인할 수 있다.


참고 : https://github.com/kubernetes/kubernetes/issues/89657

bash에서 vim 설치(feat.한글 설정)

bash: vim: command not found

경량화를 추구하는 컨테이너에는 기본 에디터가 제공되지 않으므로 필요 시 직접 설치해야 한다.

간단하게 bash에서 vim 에디터를 설치하는 방법과 한글 설정(UTF-8, CP949)까지 알아보자.

1. vim 에디터 설치

$ apt-get update

Hit:1 http://deb.debian.org/debian bookworm InRelease
Get:2 http://deb.debian.org/debian bookworm-updates InRelease [55.4 kB]
Hit:3 http://deb.debian.org/debian-security bookworm-security InRelease
$ apt install vim

Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following additional packages will be installed:
  libsodium23 vim-common vim-runtime xxd
Suggested packages:
  ctags vim-doc vim-scripts
The following NEW packages will be installed:
  libsodium23 vim vim-common vim-runtime xxd
0 upgraded, 5 newly installed, 0 to remove and 40 not upgraded.
Need to get 8962 kB of archives.
After this operation, 41.8 MB of additional disk space will be used.
Do you want to continue? [Y/n] 

bash에서 두 커맨드를 실행하고 y를 눌러주면 끝이다(권한 문제가 발생하면 sudo를 앞에 붙인다).

그런데 한글이나 일본어 등을 사용하려면 인코딩이 필요하다.

UTF-8을 설정해보자.

vim 인코딩 설정(UTF-8)

설치한 vim을 사용해 /etc/vim/vimrc을 열고 하단에 다음 내용을 추가한다.

" Source a global configuration file if available
if filereaedable("/etc/vim/vimrc.local")
  source /etc/vim/vimrc.local
endif

//추가하는 부분
set encoding=utf-8
set fileencodings=utf-8,cp949

utf-8은 mac, cp949는 windows에서 사용하는 인코딩 방식이다.

저장하면 vim에서 한글 등이 사람 언어로 표시된다.

알기 쉬운 BEM 알아보기(CSS)

block__element_modifier모듈 기반의 방법론 (+mix)

공식 문서(https://en.bem.info)에서는 BEM을 이렇게 설명한다.

BEM(Block, Element, Modifier)는 컴포넌트 기반 접근 방식이다. 기본 아이디어는 사용자 인터페이스(UI)를 독립된 블록으로 나누는 것이다. 이를 통해 복잡한 UI도 쉽고 빠르게 개발할 수 있으며, 기존 코드를 재사용할 수 있는 장점이 있다.

BEM은 모듈을 Block, Element, Modifier 단위로 분해하며, 각 앞 글자를 따서 BEM으로 부른다. BEM은 러시아 Yandex사에서 개발한 방법론으로 실제로 이 방법에 기반한 CSS 설계 기법이 널리 사용되고 있지만 BEM은 CSS에만 국한되지는 않는다.

그렇다면 BEM의 공통 규칙과 모듈의 개별 규칙을 알아보자.

CSS style을 위한 BEM 공통 규칙

– ID 셀렉터와 태그 셀렉터를 사용하지 않는다.
– 네스팅된 셀렉터 사용을 최소화한다.
– CSS 클래스 네이밍 컨벤션을 사용하여 이름 충돌을 피하고 셀렉터의 목적을 명확하게 한다.
– Blocks, Elements, Modifiers를 구분하여 사용한다.
– Blocks를 재사용한다.

BEM의 네이밍 규칙

모두 소문자를 사용하며, 두 단어는 하이픈(-)으로 연결한다.

Blockblock, block-two
Elementblock 이름을 상속받아 언더스코어 두 개(__)로 연결block__element, block-two__element-two
ModifierBlock/Element 이름 상속받아 언더스코어 하나(_)로 연결. 키-값 쌍은 스네이크 케이스(_)를 사용.
block_modifier, block__element_modifier, element_key_value
Modifier
(MindBEMding)
Block/Element 이름 상속받아 하이픈 두개(–)로 연결. 키-값에서 키는 생략가능.block–modifier, element–value

모듈의 개별 규칙

논리적, 기능적으로 독립되어 재사용이 가능한 모듈로 정의한다. 클래스 이름은 ‘형태’가 아니라 ‘목적, 용도’를 나타내야 한다.

EX) head, logo, menu (O)
blue-button, small-title (X)

block은 다른 block 내부에서도 사용할 수 있으며, modifier를 통해 위치나 모양을 변경할 수 있다.

그림 – block의 중첩(출처: en.bem.info)

Element

block을 구성하며 block 내부에서만 사용하는 모듈로 정의한다. 예를 들어 menu__item은 menu라는 block을 구성하며 menu 밖에서는 사용할 수 없으므로 element가 된다.

그림 – block을 구성하는 element(출처:en.bem.info)


element 클래스 이름도 block과 동일하게 ‘형태’가 아니라 ‘용도’를 나타내야 한다.
element는 선택적 요소이므로 사용하지 않아도 상관없지만 element 이름을 중첩하여 사용하지 않는다.

EX) block__element__element, menu__button__icon (X)

Modifier

block/element의 모양이나 동작을 정의하는 모듈이다.
modifier는 선택적 요소이므로 사용하지 않아도 상관없지만 단독으로는 사용할 수 없고 block/element와 함께 사용해야 한다.
같은 block이라도 modifier에 따라 모양이 변할 수 있으며, modifier는 런타임이나 block에 따라 바뀔 수 있다.
modifier 클래스 이름은 형태(크기, 색, 상태, 동작)을 나타내야 한다. 형태로 나누면 boolean, key-value 두 가지가 있으며, 각각의 예는 다음과 같다.

– boolean : ex) active, disabled
– key-value : ex) color_red, line_doubled (스네이크 케이스로 표현)

modifier의 수는 제한이 없으므로 여러 개를 붙일 수 있지만 동일한 스타일은 중복하지 않는다.

MIX

mix는 하나의 요소에 역할이 다른 여러 클래스를 사용하는 기법이다.
장점은 코드를 복사하지 않고 기존의 스타일과 조합해서 새로운 모듈로 사용할 수 있다.
block+element, block+block, element+element 등의 mix를 사용할 수 있으며, 샘플 코드를 통해 확인할 수 있다.

<!-- HTML -->
<div class="header">
  <div class="logo header__logo"></div>
</div>

/* CSS */
.logo {
  width:100px;
  height:50px;
}
.header__logo {
  margin: 12px;
}

다른 요소와 관계된 레이아웃과 관련된 부분은 mix, block 내부의 문제는 modifier를 사용하는 것이 좋다.
mix는 독립성과 재사용성이 높은 상태를 유지하는 장점이 있다.


참고
https://en.bem.info
– 다양한 예제로 배우는 CSS 설계 실전 가이드


CSS 설계를 위한 기본 가이드

변경과 확장에 강하고 안정적인 CSS 구조를 위한 기법

다양한 예제로 배우는 CSS 설계 실전가이드에서 소개하는 설계의 핵심을 알아보자.

CSS의 여러 설계 기법들도 결국에는 다음 여덟 가지 리스트에 속한다고 하니 핵심을 관통하는 포인트라고 볼 수 있다.

1. 특성에 따른 CSS 분류
2. 느슨한 스타일의 결합
3. 적당한 영향 범위
4. 특정 컨텍스트에 대한 적절한 의존도
5. 적절한 명시도(specificity)
6. 영향 범위가 유추 가능한 클래스 네임
7. 형태, 기능, 역할의 유추가 가능한 클래스 네임
8. 확장 용이성

1. 특성에 따른 CSS 분류

첫 번째는 CSS 역할이나 특성에 따라 분류하는 것이다.

베이스 그룹 : 사이트에서 베이스가 되거나 공통으로 적용되는 사항 등
레이아웃 그룹 : 헤더(header), 푸터(footer), 콘텐츠(content) 영역 등
모듈 그룹 : 재사용되는 모듈 등

모듈 자체에는 레이아웃과 관련된 부분은 설정하지 않는 것이 좋다. 모듈은 자신의 레이아웃에는 관여하지 않고 해당 모듈 자체의 역할 및 자녀 요소의 스타일링에만 관심을 가져야 한다. 여기서 레이아웃과 관련된 부분이란 다음과 같다.

-position, z-index, top/right/bottom/left, float, width, margin

/* 베이스 */
body {
  font-family: 'Noto Sans';
}

/* 레이아웃 */
layout_content {
  width: 1000px;
  padding: 10px;
  margin: 0 auto;
}

/* 모듈 */
.bl_media {
  display: flex;
  justify-items: center;
  align-items: center;
}

.bl_media_title {
  margin: 5px;
  font-weight: bold;
}

2. 느슨한 스타일의 결합

정확히는 HTML과 스타일링의 느슨한 결합이다. 느슨한 결합이란 의존하지 않는 상태를 이야기하는데 가장 간단한 방법으로는 요소형 셀렉터를 피하고 클래스 셀렉터를 사용하는 것이다.

p{}, div{}, h1{}과 같이 요소의 스타일을 직접 정의하는 요소형 셀렉터는 요소의 변경이 발생하게 되면 스타일이 중복되거나 누락될 가능성이 크므로 클래스를 사용해서 정의하는 것이 좋다.

3. 적당한 영향 범위

영향 범위를 가능한 좁게 하거나 영향 범위가 넓은 CSS의 스타일을 최소화한다.

영향 범위를 고려하지 않은 채 스타일을 추가하다보면 의도하지 않은 부분에서 오류가 발생하므로 최대한 범위를 줄이기 위해 가장 가까운 부모 요소를 포함시키거나 손자와 자녀 셀렉터의 사용도 함께 검토하는 것이 좋다.

4. 특정 컨텍스트에 대한 적절한 의존도

컨텍스트란 위치 또는 상황을 의미하며, 이 컨텍스트가 변할 때 코드가 동작하지 않을 수 있는 상황을 주의해야 한다. 사용 목적이 명확하다면 문제없지만 그렇지 않은 경우에는 셀렉터를 불필요한 부분까지 구체화하게 되면 해당 문제가 발생하기 쉽다.

/* id인 main을 지정하게 되면 해당 모듈 내부에서만 사용 가능한 .title_top 클래스가 되어 의존도가 높아짐. -> 불필요한 #main 제거 */
#main .title_top{
  display:flex;
  align-items: center;
  color: green;
}

5. 적절한 명시도(specificy)

우선순위를 나타내는 명시도와 관련된 내용은 여기를 클릭하면 확인할 수 있다.

명시도가 높은 CSS의 단점은 다음과 같다.

– 셀렉터 예측이 어려움
– 다른 요소(부모 요소 등)에 대한 의존도 상승
– 덮어쓰기의 어려움
– 유지 보수의 어려움

당연히 !important는 사용하지 않는 것이 기본이며, 셀렉터를 사용할 때는 클래스 셀렉터를 사용한다. id 셀렉터는 우선순위가 매우 높으며 한 페이지 안에서 동일한 값은 한번 밖에 사용하지 못하는 제약도 있으므로 id를 사용하는 이점은 많지 않다.

따라서 기본적으로 클래스 셀렉터를 사용해서 의도하지 않은 우선 순위가 뒤섞이지 않도록 해야 한다.

6. 영향 범위가 유추 가능한 클래스 네임

규모가 커질수록 모듈이나 클래스도 늘어나므로 하나의 클래스 수정이 어떤 범위까지 영향을 미칠지를 이름에서 판단할 수 있도록 해야 한다.

HTML에서 정의된 요소의 클래스 네임만 보고도 CSS의 영향 범위를 유추할 수 있도록 하자.

클래스 네임으로 영향 범위를 쉽게 유추하기 위해서는 자녀 요소에 루트 요소의 클래스 네임을 포함하도록 하는 것이 좋다.

7. 형태, 기능, 역할의 유추가 가능한 클래스 네임

영향 범위와 마찬가지로 형태, 기능, 역할도 유추가 가능하도록 네임을 지정해야 한다.

– title1, title2, title3
– main-title, nav-title, sub-title

두 항목 중 형태나 기능, 역할의 유추가 쉬운 쪽이 어디인지는 분명하다. 각 모듈의 기능이나 역할에 맞춰 이름을 붙이는 것이 매우 중요하다. 특히 작은 규모의 코드라면 title1, title2와 같이 네임을 대충 지정하기 쉽다. 하지만 규모란 언제 어떤 방식으로 커질지 모르므로 항상 습관적으로 이름에서 형태, 기능, 역할을 유추할 수 있고 구체성과 범용성의 균형을 맞추도록 하는 노력이 필요하다.

8. 확장 용이성

확장성이란 결국 기능의 추가나 유지와 연결되는 부분으로, 항상 가능성을 열어두어야 한다.

확장이 쉽도록 클래스를 설계하거나 추가하는 클래스에는 기능, 역할에 따라 적절한 상세도와 영향 범위를 갖도록 한다.

확장이 쉬운 클래스란 멀티 클래스를 사용하는 것으로, 하나의 클래스에는 보편적인 속성, 추가 클래스에는 특정 속성을 추가하여 덮어쓰기를 통해 여러 클래스로 스타일을 지정하는 방식이다.

이를 통해 HTML은 복잡해지지만 CSS는 매우 간단해지는 트레이드오프가 발생하기도 한다. 하지만 멀티 클래스의 이점은 불규칙한 상황에서도 클래스 하나만으로 원하는 작업을 완료할 수 있는 간결함이다.

결국 모든 항목은 별개의 사항이 아니라 다 연결되어 있음을 알 수 있고, 작은 수정 하나가 큰 나비효과를 불러올 수도 있다는 점을 명심해야 한다.

매번 하나하나 체크해가면서 코드를 작성하기보다는 작성 방식이 고민되거나 자율성이 주어졌을 때, 해당 리스트를 떠올리면서 작성하다보면 생각보다도 더 견고한 구조를 만들 수 있을 것이다.


참고 : 다양한 예제로 배우는 CSS 설계 실전가이드(2021, 제이펍)

자바스크립트, class를 문법적 설탕(syntactic sugar)이라 부르는 이유

function으로 구현하는 class의 동작 방식

‘문법적 설탕’은 통용되는 단어이지만 어감이 이상한 건 어쩔 수 없다. 의역하여 ‘편리한 구문 용법’ 정도가 더 이해하기 쉬울 것 같다.

위키에서 syntactic sugar의 정의는 다음과 같다.

In computer science, syntactic sugar is syntax within a programming language that is designed to make things easier to read or to express.
컴퓨터 과학 분야에서 ‘편리한 구문 용법’이란 읽기 쉽고 표현하기 쉽도록 구성된 프로그래밍 언어 내의 구문이다.

즉, 달콤한 초콜릿처럼 손이 자주 가는 구문이라고 볼 수 있을 것 같다.

자바스크립트는 처음부터 모든 기능을 갖고 태어나지 않았으므로 ES 버전으로 대표되는 추가 기능은 기존의 문법으로 구현된 것이 많다.

class 또한 기존의 OOP에서 차용한 개념을 자바스크립트에서 구현한 것으로 구현 방식은 자바스크립트의 문법을 벗어나지 않는다.

따라서 사용자가 선언하고 사용하는 방식은 다르지만 다음에서 확인할 두 코드의 작동 방식은 동일하다.

function으로 선언된 부분이 기본 동작 방식이며 class가 선언되면 이 function과 완전히 동일한 방식으로 구현된다.

같은 기능을 구현할 때 function과 class 중 어떤 것에 더 선뜻 손이 가는지를 묻는다면 설탕의 의미를 생각해볼 수 있다.

function 사용

function createGame( user, point ) {
  this.user = user;
  this.point = point;
}

createGame.prototype.addPoint = function() { this.point++; };

const myGame = new createGame("Yoon", 0);

myGame.addPoint();

class 사용

class CreateGame {
  constructor (user, point) {
    this.user = user;
    this.point = point;
  }
  addPoint() { this.point++; }
}

const myGame = new CreateGame("Yoon", 0);
myGame.addPoint();