Navigation

2. 소유와 경계

CSS 파일마다 소유자를 정하고, 다른 소유자의 스타일을 사용할 때 지킬 범위를 명시합니다. 외부 라이브러리 DOM은 소유한 루트 아래에서만 선택해 다른 화면에 영향을 주지 않습니다.

2.1 CSS 파일마다 고유한 범위_식별자를 하나씩 씁니다

파일마다 네임스페이스를 구분해 전역 클래스 충돌을 막습니다.

CSS 파일마다 고유한 범위_식별자를 하나씩 씁니다. 같은 범위_식별자를 여러 파일에서 나누어 쓰지 않습니다.

상황처리
새 스타일을 추가함같은 범위_식별자를 쓰는 파일이 있는지 먼저 확인합니다
의미가 같아도 CSS 파일이 다름식별자를 따로 만듭니다. 부품끼리 부모 식별자를 나누어 쓰는 것도 금지합니다
부모 식별자를 계속 씀스타일도 부모 CSS 파일에 둡니다

이미 다른 소유자가 쓰는 scope_slug를 재사용합니다.

/* products route */
pg_products__header

/* orders route */
pg_products__header

소유자가 다르면 별도 식별자를 부여합니다.

/* products route */
pg_products__header

/* orders route */
pg_orders__header

부품의 CSS 파일이 부모 식별자를 그대로 씁니다.

page/product-detail/_pg-chart-card.csstext
pg_productDetail__chartCard

자기 CSS 파일을 가진 컴포넌트는 자기 식별자를 씁니다.

page/product-detail/_pg-chart-card.csstext
pg_chartCard__root

2.2 범위 접두사는 소유 레이어를 따릅니다

클래스 접두사로 소유 레이어를 구분합니다.

범위 접두사는 CSS 파일 소유자의 레이어를 나타내며, 폴더 깊이가 아니라 최상위 폴더로 정합니다.

접두사최상위 폴더소유 레이어
pg_src/page화면을 아는 라우트 진입 파일과 컴포넌트
wg_src/component/widget도메인은 알고 화면은 모르는 컴포넌트
ui_src/component/ui도메인도 화면도 모르는 컴포넌트

라우트 진입 파일과 부품, widget 내부 부품 모두 최상위 폴더의 접두사를 씁니다. 사용 횟수로 레이어를 바꾸지 않으며, 소유자의 레이어가 바뀌면 접두사도 함께 바꿉니다. 어느 최상위 폴더에 둘지와 파일 이름의 _ 표식은 활성화된 프레임워크 규약이 정합니다.

최상위 폴더 대신 사용 횟수와 재사용 예상을 보고 접두사를 고릅니다.

page/product-detail/_pg-product-table-section.css
  wg_productTableSection__root

component/widget/chart-card/_wg-chart-card-header.css
  pg_chartCard__header

소유 레이어대로 접두사를 붙입니다.

page/product-detail/pg-product-detail.css
  pg_productDetail__root

page/product-detail/_pg-product-table-section.css
  pg_productTableSection__root

component/widget/chart-card/_wg-chart-card-header.css
  wg_chartCard__header

component/ui/button/ui-button.css
  ui_button__root

함께 보기

2.3 다른 소유자의 클래스는 내 최상위 블록 안에서만 씁니다

다른 소유자의 스타일을 덮어써도 해당 인스턴스에만 적용되도록 제한합니다.

선택자 판정

다른 소유자의 클래스는 내 최상위 클래스 블록 안에서 &로 시작하는 선택자로만 씁니다. 내 scope_slug와 다르면 외부 라이브러리, 다른 화면, widget 모두 같은 기준을 적용합니다.

flowchart LR
	q1{"내 scope_slug<br>클래스인가?"} -- 아니요 --> q2{"내 최상위 블록 안에서<br>&로 시작하는가?"} -- 예 --> r2("허용")
	q1 -- 예 --> r1("이 규칙의 대상 아님")
	q2 -- 아니요 --> r3("금지")
선택자판정
.MuiTreeItem-label { }금지. 그 라이브러리를 쓰는 앱 전체에 적용됩니다
.wg_chartCard__caption { }금지. 그 widget을 쓰는 모든 화면에 적용됩니다
.pg_products__sidebar { & .MuiTreeItem-label { } }허용. 해당 인스턴스에만 적용됩니다
.pg_productDetail__root { & .wg_chartCard__caption { } }허용
.pg_products__sidebar .MuiTreeItem-label { }금지. 최상위 블록 안에서 &로 시작해야 합니다
.pg_products__sidebarToolbar .pg_products__sidebarTitle { }같은 소유자의 클래스끼리라 이 규칙의 대상이 아닙니다

다른 소유자의 DOM 경로는 우리가 정하지 않으므로 결합자 개수를 제한하지 않습니다. 블록 중첩 깊이는 규칙 4.1를 따릅니다. 직접 수정할 수 있는 클래스라면 규칙 2.4의 세 방법을 먼저 확인하고, 모두 맞지 않을 때 이 규칙을 적용합니다.

기계 검사 범위

selector-disallowed-list는 등록된 외부 접두사와 다른 레이어의 최상위 클래스를 검사합니다. 같은 레이어의 다른 식별자와 미등록 라이브러리 클래스는 이 검사에 걸리지 않아 파일별 소유자를 대조해야 합니다.

최상위 블록 없이 라이브러리 클래스를 바로 씁니다.

.MuiTreeItem-content {
	border-radius: 4px;
}

.MuiTreeItem-label {
	color: #8c8c8c;
}

내 최상위 블록 안에서 외부 라이브러리 DOM을 선택자로 잡습니다.

.pg_products__sidebar {
	& .MuiTreeItem-content {
		border-radius: 4px;
	}

	& .MuiTreeItem-label {
		color: #8c8c8c;
	}
}

최상위 블록 없이 다른 scope_slug의 클래스를 바로 씁니다.

page/product-detail/pg-product-detail.csscss
.wg_chartCard__caption {
	letter-spacing: 0.02em;
}

.ui_card__title {
	font-size: 13px;
}

다른 scope_slug의 클래스도 내 최상위 블록 안에서 선택자로 잡습니다.

page/product-detail/pg-product-detail.csscss
.pg_productDetail__chartSlot {
	min-height: 240px;

	& .wg_chartCard__caption {
		letter-spacing: 0.02em;
	}

	& .ui_card__title {
		font-size: 13px;
	}
}

최상위 블록을 열지 않고 바깥에서 이어 씁니다.

.pg_products__sidebarToolbar > .MuiButton-root > .MuiButton-startIcon {
	color: #8c8c8c;
}

소유자 API로 해결할 수 없으면 내 최상위 블록 안에서 선택합니다.

.pg_products__sidebarToolbar {
	& > .MuiButton-root > .MuiButton-startIcon {
		color: #8c8c8c;
	}
}

함께 보기

2.4 다른 소유자의 모습은 해당 소유자의 API로 바꿉니다

다른 소유자의 모습을 바꿀 때 배치 조정, 변형 노출, 레이어 이동을 순서대로 판단합니다.

다른 소유자의 모습을 바꿀 때는 아래 세 방법을 순서대로 확인합니다.

flowchart LR
	q1{"최상위 배치만<br>다른가?"} -- 아니요 --> q2{"여러 사용처에서<br>같은 내부 모습인가?"} -- 아니요 --> q3{"레이어 판정이<br>화면 소유인가?"} -- 아니요 --> r4("내 최상위 블록에서<br>선택자로 지정")
	q1 -- 예 --> r1("사용처가<br>className 전달")
	q2 -- 예 --> r2("소유자가<br>variant 프롭 노출")
	q3 -- 예 --> r3("화면 폴더로<br>파일 이동")
상황방법수정 위치
최상위 배치만 다름사용처가 className을 넘겨 자기 클래스로 스타일을 줍니다사용처 TSX와 CSS
내부 모습이 여러 사용처에서 같게 반복됨소유자가 variant 프롭으로 수정자를 노출합니다소유자 TSX와 CSS, 사용처 TSX
레이어 판정 결과가 화면 소유임프롭을 추가하지 않고 화면 폴더로 파일을 옮깁니다파일 위치와 접두사

화면 소유 여부는 사용 횟수가 아니라 활성화된 프레임워크 규약으로 판단합니다. 세 방법이 모두 맞지 않으면 규칙 2.3에 따라 내 최상위 블록 안에서 선택자로 지정합니다. className을 최상위까지만 전달하는 경계는 규칙 3.4가 정합니다.

최상위 배치를 className으로 바꿀 수 있는데도 다른 소유자의 클래스를 선택합니다.

<WgChartCard dailyCounts={dailyCounts} />
page/product-detail/pg-product-detail.csscss
.pg_productDetail__root {
	& .wg_chartCard__root {
		grid-area: chart;
		margin-block-end: 16px;
	}
}

최상위 배치는 사용처가 자기 클래스로 잡습니다.

<WgChartCard dailyCounts={dailyCounts} className={clsx("pg_productDetail__chartCard")} />
page/product-detail/pg-product-detail.csscss
.pg_productDetail__chartCard {
	grid-area: chart;
	margin-block-end: 16px;
}

여러 화면이 쓰는 모양은 소유자가 variant 프롭으로 노출합니다.

<WgChartCard dailyCounts={dailyCounts} variant="muted" />
component/widget/chart-card/wg-chart-card.csscss
.wg_chartCard__caption--muted {
	color: #8c8c8c;
}

화면 소유로 판정한 컴포넌트를 화면 폴더로 옮깁니다.

before
  component/widget/chart-card/wg-chart-card.tsx      product-detail 화면의 뷰모델 타입을 받음
  component/widget/chart-card/wg-chart-card.css      pg_productDetail만 내부를 덮어쓰고 있었음

after
  page/product-detail/_pg-chart-card.tsx
  page/product-detail/_pg-chart-card.css  pg_chartCard__*로 소유자 하나

함께 보기