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 파일이 부모 식별자를 그대로 씁니다.
pg_productDetail__chartCard자기 CSS 파일을 가진 컴포넌트는 자기 식별자를 씁니다.
pg_chartCard__root2.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의 클래스를 바로 씁니다.
.wg_chartCard__caption {
letter-spacing: 0.02em;
}
.ui_card__title {
font-size: 13px;
}다른 scope_slug의 클래스도 내 최상위 블록 안에서 선택자로 잡습니다.
.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로 바꿉니다
- 2.1 CSS 파일마다 고유한 범위_식별자를 하나씩 씁니다
- 4.1 선택자 중첩은 한 겹까지만 쓰고 나머지 경로는 한 줄로 잇습니다
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} />
.pg_productDetail__root {
& .wg_chartCard__root {
grid-area: chart;
margin-block-end: 16px;
}
}최상위 배치는 사용처가 자기 클래스로 잡습니다.
<WgChartCard dailyCounts={dailyCounts} className={clsx("pg_productDetail__chartCard")} />
.pg_productDetail__chartCard {
grid-area: chart;
margin-block-end: 16px;
}여러 화면이 쓰는 모양은 소유자가 variant 프롭으로 노출합니다.
<WgChartCard dailyCounts={dailyCounts} variant="muted" />
.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__*로 소유자 하나
함께 보기