성능 향상을 위해 Sass에서 사용하지 않는 CSS 처리하기
게시 됨: 2022-03-10현대 프론트엔드 개발에서 개발자는 확장 가능하고 유지 관리 가능한 CSS 작성을 목표로 해야 합니다. 그렇지 않으면 코드베이스가 커지고 더 많은 개발자가 기여함에 따라 캐스케이드 및 선택기 특이성과 같은 세부 사항에 대한 제어 권한을 잃을 위험이 있습니다.
이를 달성할 수 있는 한 가지 방법은 OOCSS(Object-Oriented CSS)와 같은 방법론을 사용하는 것입니다. OOCSS는 페이지 컨텍스트를 중심으로 CSS를 구성하기보다 장식(글꼴, 브랜드, 색상 등).
따라서 다음과 같은 CSS 클래스 이름:
-
.blog-right-column
-
.latest_topics_list
-
.job-vacancy-ad
동일한 CSS 스타일을 적용하지만 특정 컨텍스트에 연결되지 않는 재사용 가능한 대안으로 대체됩니다.
-
.col-md-4
-
.list-group
-
.card
이 접근 방식은 일반적으로 Bootstrap, Foundation과 같은 Sass 프레임워크의 도움으로 구현되거나 점점 더 자주 프로젝트에 더 잘 맞도록 형성될 수 있는 맞춤형 프레임워크의 도움으로 구현됩니다.
이제 우리는 패턴, UI 구성 요소 및 유틸리티 클래스의 프레임워크에서 선별된 CSS 클래스를 사용하고 있습니다. 아래 예는 수직으로 쌓인 다음 md 중단점에 도달하면 3열 레이아웃으로 전환하는 부트스트랩을 사용하여 구축된 일반적인 그리드 시스템을 보여줍니다.
<div class="container"> <div class="row"> <div class="col-12 col-md-4">Column 1</div> <div class="col-12 col-md-4">Column 2</div> <div class="col-12 col-md-4">Column 3</div> </div> </div>
.col-12
및 .col-md-4
와 같은 프로그래밍 방식으로 생성된 클래스는 여기에서 이 패턴을 만드는 데 사용됩니다. 하지만 .col-1
부터 .col-11
, .col-lg-4
, .col-md-6
또는 .col-sm-12
어떻습니까? 이들은 모두 컴파일된 CSS 스타일시트에 포함될 클래스의 예이며 사용 중이 아님에도 불구하고 브라우저에서 다운로드 및 구문 분석합니다.
이 기사에서는 사용하지 않는 CSS가 페이지 로드 속도에 미칠 수 있는 영향을 살펴보는 것으로 시작하겠습니다. 그런 다음 스타일시트에서 제거하기 위한 기존 솔루션을 살펴보고 Sass 지향 솔루션을 따르도록 하겠습니다.
사용하지 않는 CSS 클래스의 영향 측정
강력한 블레이드인 Sheffield United를 좋아하지만 웹 사이트의 CSS는 단일 568kb 축소 파일로 번들되어 gzip으로 압축해도 105kb에 이릅니다. 많은 것 같습니다.
실제로 홈페이지에서 이 CSS를 얼마나 사용하는지 볼까요? 빠른 Google 검색을 통해 작업에 대한 많은 온라인 도구를 알 수 있지만 Chrome의 DevTools에서 바로 실행할 수 있는 Chrome의 적용 도구를 사용하는 것을 선호합니다. 소용돌이를 주자.
결과는 568kb 스타일시트에서 30kb 의 CSS만 홈페이지에서 사용하고 나머지 538kb는 웹사이트의 나머지 부분에 필요한 스타일과 관련되어 있음을 보여줍니다. 이는 CSS의 무려 94.8% 가 사용되지 않음을 의미합니다.
CSS는 페이지 렌더링을 시작하기 전에 브라우저가 완료해야 하는 모든 다른 단계를 포함하는 웹 페이지의 중요한 렌더링 경로의 일부입니다. 이것은 CSS를 렌더링 차단 자산으로 만듭니다.
따라서 이를 염두에 두고 양호한 3G 연결을 사용하여 Sheffield United의 웹사이트를 로드할 때 CSS가 다운로드되고 페이지 렌더링이 시작되기까지 전체 1.15초가 걸립니다. 이것은 문제입니다.
구글도 이를 인정했다. Lighthouse 감사를 온라인이나 브라우저를 통해 실행할 때 사용하지 않는 CSS를 제거하여 잠재적인 로드 시간과 파일 크기 절감 효과가 강조 표시됩니다.
기존 솔루션
목표는 어떤 CSS 클래스가 필요하지 않은지 확인하고 스타일시트에서 제거하는 것입니다. 이 프로세스를 자동화하려는 기존 솔루션을 사용할 수 있습니다. 일반적으로 Node.js 빌드 스크립트 또는 Gulp와 같은 작업 실행기를 통해 사용할 수 있습니다. 여기에는 다음이 포함됩니다.
- UNCSS
- 퓨리파이CSS
- 퍼지 CSS
일반적으로 비슷한 방식으로 작동합니다.
- Bulld에서 웹사이트는 헤드리스 브라우저(예: puppeteer) 또는 DOM 에뮬레이션(예: jsdom)을 통해 액세스됩니다.
- 페이지의 HTML 요소를 기반으로 사용되지 않은 CSS가 식별됩니다.
- 이것은 스타일시트에서 제거되고 필요한 것만 남겨둡니다.
이러한 자동화된 도구는 완벽하게 유효하고 여러 상업 프로젝트에서 많은 도구를 성공적으로 사용했지만 공유할 가치가 있는 몇 가지 단점이 있습니다.
- 클래스 이름에 '@' 또는 '/'와 같은 특수 문자가 포함된 경우 일부 사용자 정의 코드를 작성하지 않으면 인식되지 않을 수 있습니다. 저는 Harry Roberts의 BEM-IT를 사용합니다. 여기에는
u-width-6/12@lg
와 같은 반응형 접미사로 클래스 이름을 구조화하는 작업이 포함되므로 이전에 이 문제를 다룬 적이 있습니다. - 웹 사이트에서 자동화된 배포를 사용하는 경우 특히 페이지와 CSS가 많은 경우 빌드 프로세스가 느려질 수 있습니다.
- 이러한 도구에 대한 지식은 팀 전체에서 공유해야 합니다. 그렇지 않으면 프로덕션 스타일시트에 CSS가 불가사의하게 없을 때 혼란과 좌절이 있을 수 있습니다.
- 웹 사이트에서 실행 중인 타사 스크립트가 많은 경우 헤드리스 브라우저에서 열 때 이러한 스크립트가 제대로 재생되지 않고 필터링 프로세스에 오류가 발생할 수 있습니다. 따라서 일반적으로 설정에 따라 까다로울 수 있는 헤드리스 브라우저가 감지될 때 타사 스크립트를 제외하는 사용자 지정 코드를 작성해야 합니다.
- 일반적으로 이러한 종류의 도구는 복잡하고 빌드 프로세스에 많은 추가 종속성을 도입합니다. 모든 타사 종속성의 경우와 마찬가지로 이것은 다른 사람의 코드에 의존한다는 것을 의미합니다.
이러한 점을 염두에 두고 나는 스스로에게 다음과 같은 질문을 던졌습니다.
Sass만 사용하면 Sass에서 소스 클래스를 완전히 삭제하지 않고 사용하지 않는 CSS를 제외할 수 있도록 컴파일한 Sass를 더 잘 처리할 수 있습니까?
스포일러 경고: 대답은 예입니다. 여기 내가 생각해낸 것이 있습니다.
Sass 지향 솔루션
솔루션은 Sass가 컴파일해야 할 대상을 선택하는 빠르고 쉬운 방법을 제공해야 하며, 개발 프로세스에 더 이상 복잡성을 추가하거나 개발자가 프로그래밍 방식으로 생성된 CSS와 같은 것을 활용하는 것을 방지할 만큼 충분히 간단해야 합니다. 클래스.
시작하려면 여기에서 복제할 수 있는 빌드 스크립트와 몇 가지 샘플 스타일이 있는 리포지토리가 있습니다.
팁: 막히면 마스터 브랜치에서 완료된 버전과 항상 상호 참조할 수 있습니다.
cd
를 repo에 넣고 npm install
을 실행한 다음 npm run build
를 실행하여 필요에 따라 Sass를 CSS로 컴파일합니다. 이것은 dist 디렉토리에 55kb CSS 파일을 생성해야 합니다.
그런 다음 웹 브라우저에서 /dist/index.html
을 열면 클릭 시 확장되어 일부 콘텐츠가 표시되는 상당히 표준적인 구성 요소가 표시되어야 합니다. 또한 여기에서 실제 네트워크 조건이 적용되는 것을 볼 수 있으므로 자체 테스트를 실행할 수 있습니다.
부분 수준에서 필터링
일반적인 SCSS 설정에서는 단일 매니페스트 파일(예: 리포지토리의 main.scss
) 또는 페이지당 하나(예: index.scss
, products.scss
, contact.scss
)가 있을 수 있습니다. 수입됩니다. OOCSS 원칙에 따라 이러한 가져오기는 다음과 같이 보일 수 있습니다.
실시예 1
/* Undecorated design patterns */ @import 'objects/box'; @import 'objects/container'; @import 'objects/layout'; /* UI components */ @import 'components/button'; @import 'components/expander'; @import 'components/typography'; /* Highly specific helper classes */ @import 'utilities/alignments'; @import 'utilities/widths';
이러한 부분이 사용되지 않는 경우 이 사용되지 않는 CSS를 필터링하는 자연스러운 방법은 가져오기를 비활성화하여 컴파일되지 않도록 하는 것입니다.
예를 들어 확장기 구성요소만 사용하는 경우 매니페스트는 일반적으로 다음과 같습니다.
실시예 2
/* Undecorated design patterns */ // @import 'objects/box'; // @import 'objects/container'; // @import 'objects/layout'; /* UI components */ // @import 'components/button'; @import 'components/expander'; // @import 'components/typography'; /* Highly specific helper classes */ // @import 'utilities/alignments'; // @import 'utilities/widths';
그러나 OOCSS에 따라 최대 재사용성을 허용하기 위해 구조에서 장식을 분리하고 있으므로 확장기가 올바르게 렌더링하기 위해 다른 개체, 구성 요소 또는 유틸리티 클래스의 CSS를 요구할 수 있습니다. 개발자가 HTML을 검사하여 이러한 관계를 인식하지 않는 한 이러한 부분을 가져오는 것을 알지 못할 수 있으므로 필요한 클래스가 모두 컴파일되지는 않습니다.
repo에서 dist/index.html
에 있는 확장기의 HTML을 보면 이것이 사실인 것 같습니다. 상자 및 레이아웃 개체, 타이포그래피 구성 요소, 너비 및 정렬 유틸리티의 스타일을 사용합니다.
dist/index.html
<div class="c-expander"> <div class="o-box o-box--spacing-small c-expander__trigger c-expander__header" tabindex="0"> <div class="o-layout o-layout--fit u-flex-middle"> <div class="o-layout__item u-width-grow"> <h2 class="c-type-echo">Toggle Expander</h2> </div> <div class="o-layout__item u-width-shrink"> <div class="c-expander__header-icon"></div> </div> </div> </div> <div class="c-expander__content"> <div class="o-box o-box--spacing-small"> Lorum ipsum <p class="u-align-center"> <button class="c-expander__trigger c-button">Close</button> </p> </div> </div> </div>
이러한 관계를 Sass 자체 내에서 공식화하여 발생하기를 기다리는 이 문제를 해결하여 구성 요소를 가져오면 모든 종속성도 자동으로 가져옵니다. 이렇게 하면 개발자는 더 이상 가져와야 하는 추가 정보를 알아보기 위해 HTML을 감사해야 하는 추가 오버헤드가 없습니다.
프로그래밍 방식 가져오기 맵
이 종속성 시스템이 작동하려면 매니페스트 파일의 @import
문에 단순히 주석을 달지 않고 Sass 논리가 부분 컴파일 여부를 지정해야 합니다.
src/scss/settings
에서 _imports.scss 라는 새 부분을 만들고 settings/_core.scss
_imports.scss
@import
를 만든 후 다음 SCSS 맵을 만듭니다.
src/scss/settings/_core.scss
@import 'breakpoints'; @import 'spacing'; @import 'imports';
src/scss/settings/_imports.scss
$imports: ( object: ( 'box', 'container', 'layout' ), component: ( 'button', 'expander', 'typography' ), utility: ( 'alignments', 'widths' ) );
이 맵은 예제 1의 가져오기 매니페스트와 동일한 역할을 합니다.
실시예 4
$imports: ( object: ( //'box', //'container', //'layout' ), component: ( //'button', 'expander', //'typography' ), utility: ( //'alignments', //'widths' ) );
특정 부분이 주석 처리된 경우(위와 같이) 해당 코드는 빌드 시 컴파일되어서는 안 된다는 점에서 @imports
의 표준 세트처럼 작동해야 합니다.
그러나 종속성을 자동으로 가져오기를 원하므로 올바른 상황에서 이 맵을 무시할 수도 있어야 합니다.
렌더 믹신
Sass 로직을 추가해 봅시다. src/scss/tools
_render.scss
_render.scss 를 만든 다음 해당 @import
를 tools/_core.scss
에 추가합니다.
파일에서 render()
라는 빈 믹스인을 만듭니다.
src/scss/tools/_render.scss
@mixin render() { }
믹스인에서 다음을 수행하는 Sass를 작성해야 합니다.
- 세우다()
“안녕하세요$imports
, 좋은 날씨죠? 지도에 컨테이너 개체가 있습니까?" - $ 수입
false
- 세우다()
“그건 유감입니다. 그러면 컴파일되지 않을 것 같습니다. 버튼 컴포넌트는 어떻습니까?” - $ 수입
true
- 세우다()
"멋진! 그것은 그때 컴파일되는 버튼입니다. 나를 위해 아내에게 안부를 전해주세요.”
Sass에서는 다음과 같이 번역됩니다.
src/scss/tools/_render.scss
@mixin render($name, $layer) { @if(index(map-get($imports, $layer), $name)) { @content; } }
기본적으로 $imports
변수에 부분이 포함되어 있는지 확인하고, 그렇다면 Sass의 @content
지시문을 사용하여 렌더링하면 콘텐츠 블록을 mixin에 전달할 수 있습니다.
우리는 그것을 다음과 같이 사용할 것입니다:
실시예 5
@include render('button', 'component') { .c-button { // styles et al } // any other class declarations }
이 믹스인을 사용하기 전에 약간의 개선 사항이 있습니다. 레이어 이름(오브젝트, 컴포넌트, 유틸리티 등)은 우리가 안전하게 예측할 수 있는 것이므로 조금 간소화할 수 있는 기회가 있습니다.
render mixin 선언 전에 $layer
라는 변수를 생성하고 mixins 매개변수에서 동일한 이름의 변수를 제거합니다. 이렇게:
src/scss/tools/_render.scss
$layer: null !default; @mixin render($name) { @if(index(map-get($imports, $layer), $name)) { @content; } }
이제 개체, 구성 요소 및 유틸리티 @imports
가 있는 _core.scss
부분에서 이러한 변수를 다음 값으로 다시 선언합니다. 가져올 CSS 클래스의 유형을 나타냅니다.
src/scss/objects/_core.scss
$layer: 'object'; @import 'box'; @import 'container'; @import 'layout';
src/scss/components/_core.scss
$layer: 'component'; @import 'button'; @import 'expander'; @import 'typography';
src/scss/utilities/_core.scss
$layer: 'utility'; @import 'alignments'; @import 'widths';
이렇게 하면 render()
믹스인을 사용할 때 부분 이름을 선언하기만 하면 됩니다.
아래와 같이 각 객체, 구성 요소 및 유틸리티 클래스 선언 주위에 render()
믹스인을 래핑합니다. 이렇게 하면 부분당 하나의 렌더 믹스인 사용량을 얻을 수 있습니다.
예를 들어:
src/scss/objects/_layout.scss
@include render('button') { .c-button { // styles et al } // any other class declarations }
src/scss/components/_button.scss
@include render('button') { .c-button { // styles et al } // any other class declarations }
참고: utilities/_widths.scss
의 경우, Sass에서 mixin 호출 내에서 mixin 선언을 중첩할 수 없기 때문에 전체 부분 주위에 render()
함수를 래핑하면 컴파일 시 오류가 발생합니다. 대신 아래와 같이 create-widths()
호출 주위에 render()
mixin을 래핑하십시오.
@include render('widths') { // GENERATE STANDARD WIDTHS //--------------------------------------------------------------------- // Example: .u-width-1/3 @include create-widths($utility-widths-sets); // GENERATE RESPONSIVE WIDTHS //--------------------------------------------------------------------- // Create responsive variants using settings.breakpoints // Changes width when breakpoint is hit // Example: .u-width-1/3@md @each $bp-name, $bp-value in $mq-breakpoints { @include mq(#{$bp-name}) { @include create-widths($utility-widths-sets, \@, #{$bp-name}); } } // End render }
이 설정을 사용하면 빌드 시 $imports
에서 참조하는 부분만 컴파일됩니다.
$imports
에서 주석 처리된 구성 요소를 혼합하고 일치시키고 터미널에서 npm run build
를 실행하여 시도해 보십시오.
종속성 맵
이제 프로그래밍 방식으로 부분을 가져오고 있으므로 종속성 논리 구현을 시작할 수 있습니다.
src/scss/settings
에서 _dependencies.scss
라는 새 부분을 만들고 @import
를 settings/_core.scss
에서 하지만 _imports.scss
뒤에 있는지 확인합니다. 그런 다음 그 안에 다음 SCSS 맵을 만듭니다.
src/scss/settings/_dependencies.scss
$dependencies: ( expander: ( object: ( 'box', 'layout' ), component: ( 'button', 'typography' ), utility: ( 'alignments', 'widths' ) ) );
dist/index.html에서 볼 수 있듯이 올바르게 렌더링하려면 다른 부분의 스타일이 필요하므로 여기에서 확장기 구성 요소에 대한 종속성을 선언합니다.
이 목록을 사용하여 $imports
변수의 상태에 관계없이 이러한 종속성이 항상 종속 구성 요소와 함께 컴파일된다는 논리를 작성할 수 있습니다.
$dependencies
아래에 dependency-setup()
이라는 믹스인을 만듭니다. 여기에서 다음 작업을 수행합니다.
1. 종속성 맵을 반복합니다.
@mixin dependency-setup() { @each $componentKey, $componentValue in $dependencies { } }
2. $imports
에서 구성 요소를 찾을 수 있으면 종속성 목록을 반복합니다.
@mixin dependency-setup() { $components: map-get($imports, component); @each $componentKey, $componentValue in $dependencies { @if(index($components, $componentKey)) { @each $layerKey, $layerValue in $componentValue { } } } }
3. 종속성이 $imports
에 없으면 추가합니다.
@mixin dependency-setup() { $components: map-get($imports, component); @each $componentKey, $componentValue in $dependencies { @if(index($components, $componentKey)) { @each $layerKey, $layerValue in $componentValue { @each $partKey, $partValue in $layerValue { @if not index(map-get($imports, $layerKey), $partKey) { $imports: map-merge($imports, ( $layerKey: append(map-get($imports, $layerKey), '#{$partKey}') )) !global; } } } } } }
!global
플래그를 포함하면 Sass가 mixin의 로컬 범위가 아닌 전역 범위에서 $imports
변수를 찾도록 지시합니다.
4. 그런 다음 mixin을 호출하기만 하면 됩니다.
@mixin dependency-setup() { ... } @include dependency-setup();
이제 우리가 가진 것은 향상된 부분 가져오기 시스템입니다. 여기서 구성 요소를 가져오면 개발자는 다양한 종속성 부분도 수동으로 가져올 필요가 없습니다.
확장기 구성 요소만 $imports
변수를 구성한 다음 npm run build
를 실행합니다. 컴파일된 CSS에서 모든 종속성과 함께 확장기 클래스를 볼 수 있습니다.
그러나 이는 프로그래밍 방식이든 아니든 동일한 양의 Sass를 계속 가져오기 때문에 사용하지 않는 CSS를 필터링한다는 측면에서 테이블에 새로운 것을 가져오지는 않습니다. 이를 개선해 봅시다.
향상된 종속성 가져오기
구성 요소는 종속성에서 하나의 클래스만 필요로 할 수 있으므로 계속해서 해당 종속성 클래스를 모두 가져오면 우리가 피하려고 하는 것과 동일한 불필요한 팽창이 발생합니다.
우리는 구성 요소가 필요한 종속성 클래스로만 컴파일되도록 클래스별로 더 세분화된 필터링을 허용하도록 시스템을 개선할 수 있습니다.
대부분의 디자인 패턴에는 장식이 있든 없든 패턴이 올바르게 표시되기 위해 스타일시트에 있어야 하는 최소한의 클래스가 있습니다.
BEM과 같이 확립된 명명 규칙을 사용하는 클래스 이름의 경우 일반적으로 "Block" 및 "Element" 명명 클래스가 최소한으로 필요하고 "Modifiers"는 일반적으로 선택 사항입니다.
참고: 유틸리티 클래스는 초점이 좁기 때문에 본질적으로 격리되어 있기 때문에 일반적으로 BEM 경로를 따르지 않습니다.
예를 들어, 객체 지향 CSS의 가장 잘 알려진 예인 이 미디어 객체를 살펴보세요.
<div class="o-media o-media--spacing-small"> <div class="o-media__image"> <img src="url" alt="Image"> </div> <div class="o-media__text"> Oh! </div> </div>
구성 요소에 이 항목이 종속성으로 설정된 경우 패턴을 작동시키는 데 필요한 CSS의 최소량이므로 항상 .o-media
, .o-media__image
및 .o-media__text
를 컴파일하는 것이 좋습니다. 그러나 .o-media--spacing-small
이 선택적 수정자이기 때문에 명시적으로 그렇게 말하는 경우에만 컴파일해야 합니다. 그 사용법이 모든 미디어 개체 인스턴스에서 일관되지 않을 수 있기 때문입니다.
수정자가 필요하지 않은 경우 블록과 요소 만 가져오는 방법을 포함하면서 이러한 선택적 클래스를 가져올 수 있도록 $dependencies
맵의 구조를 수정합니다.
시작하려면 dist/index.html에서 확장기 HTML을 확인하고 사용 중인 종속성 클래스를 기록해 둡니다. 아래와 같이 $dependencies
맵에 기록합니다.
src/scss/settings/_dependencies.scss
$dependencies: ( expander: ( object: ( box: ( 'o-box--spacing-small' ), layout: ( 'o-layout--fit' ) ), component: ( button: true, typography: ( 'c-type-echo', ) ), utility: ( alignments: ( 'u-flex-middle', 'u-align-center' ), widths: ( 'u-width-grow', 'u-width-shrink' ) ) ) );
값이 true로 설정되면 이를 "블록 및 요소 수준 클래스만 컴파일하고 수정자는 없음!"으로 변환합니다.
다음 단계에서는 이러한 클래스와 수동으로 가져오려는 기타(비종속성) 클래스를 저장할 화이트리스트 변수를 만드는 작업이 포함됩니다. /src/scss/settings/imports.scss
에서 $imports
다음에 $global-filter
라는 새 Sass 목록을 만듭니다.
src/scss/settings/_imports.scss
$global-filter: ();
$global-filter
의 기본 전제는 여기에 저장된 클래스가 속한 부분이 $imports
를 통해 가져오기만 하면 빌드 시 컴파일된다는 것입니다.
이러한 클래스 이름은 구성 요소 종속성인 경우 프로그래밍 방식으로 추가하거나 아래 예제와 같이 변수가 선언될 때 수동으로 추가할 수 있습니다.
전역 필터 예
$global-filter: ( 'o-box--spacing-regular@md', 'u-align-center', 'u-width-6/12@lg' );
다음으로 @dependency-setup
믹스인에 로직을 조금 더 추가해야 합니다. 그러면 $dependencies
에서 참조하는 모든 클래스가 자동으로 $global-filter
화이트리스트에 추가됩니다.
이 블록 아래:
src/scss/settings/_dependencies.scss
@if not index(map-get($imports, $layerKey), $partKey) { }
...다음 스니펫을 추가합니다.
src/scss/settings/_dependencies.scss
@each $class in $partValue { $global-filter: append($global-filter, '#{$class}', 'comma') !global; }
이것은 모든 종속성 클래스를 반복하고 $global-filter
화이트리스트에 추가합니다.
이때, dependency-setup()
믹스인 아래에 @debug
문을 추가하여 터미널에서 $global-filter
의 내용을 출력하면:
@debug $global-filter;
... 빌드 시 다음과 같은 내용이 표시되어야 합니다.
DEBUG: "o-box--spacing-small", "o-layout--fit", "c-box--rounded", "true", "true", "u-flex-middle", "u-align-center", "u-width-grow", "u-width-shrink"
이제 우리는 클래스 화이트리스트를 얻었습니다. 우리는 이것을 다른 모든 객체, 컴포넌트 및 유틸리티 부분에 적용해야 합니다.
src/scss/tools
에 _filter.scss
라는 새 부분을 만들고 도구 레이어의 _core.scss
파일에 @import
를 추가합니다.
이 새로운 부분에서는 filter()
라는 믹스인을 만들 것입니다. 이것을 사용하여 $global-filter
변수에 포함된 경우에만 클래스가 컴파일된다는 논리를 적용합니다.
간단하게 시작하여 필터가 제어하는 $class
라는 단일 매개변수를 허용하는 믹스인을 만듭니다. 다음으로 $class
가 $global-filter
화이트리스트에 포함되어 있으면 컴파일을 허용합니다.
src/scss/tools/_filter.scss
@mixin filter($class) { @if(index($global-filter, $class)) { @content; } }
부분적으로, 우리는 다음과 같이 선택적인 클래스 주위에 믹스인을 감쌀 것입니다:
@include filter('o-myobject--modifier') { .o-myobject--modifier { color: yellow; } }
이것은 .o-myobject--modifier
클래스가 $global-filter
에 포함된 경우에만 컴파일된다는 것을 의미합니다. 이 클래스는 직접 설정하거나 $dependencies
에 설정된 항목을 통해 간접적으로 설정할 수 있습니다.
리포지토리로 이동하여 filter()
믹스인을 객체 및 구성 요소 레이어의 모든 선택적 수정자 클래스에 적용합니다. 타이포그래피 구성 요소 또는 유틸리티 계층을 처리할 때 각 클래스는 다음 클래스와 독립적이므로 모두 선택 사항으로 만드는 것이 합리적이므로 필요할 때 클래스를 활성화할 수 있습니다.
다음은 몇 가지 예입니다.
src/scss/objects/_layout.scss
@include filter('o-layout__item--fit-height') { .o-layout__item--fit-height { align-self: stretch; } }
src/scss/utilities/_alignments.scss
// Changes alignment when breakpoint is hit // Example: .u-align-left@md @each $bp-name, $bp-value in $mq-breakpoints { @include mq(#{$bp-name}) { @include filter('u-align-left@#{$bp-name}') { .u-align-left\@#{$bp-name} { text-align: left !important; } } @include filter('u-align-center@#{$bp-name}') { .u-align-center\@#{$bp-name} { text-align: center !important; } } @include filter('u-align-right@#{$bp-name}') { .u-align-right\@#{$bp-name} { text-align: right !important; } } } }
참고: 반응형 접미사 클래스 이름을 filter()
믹스인에 추가할 때 '@' 기호를 '\'로 이스케이프할 필요가 없습니다.
이 과정에서 filter()
믹스인을 부분에 적용하는 동안 몇 가지 사항을 알아차렸을 수도 있고 알아차리지 못했을 수도 있습니다.
그룹화된 수업
코드베이스의 일부 클래스는 함께 그룹화되고 동일한 스타일을 공유합니다. 예를 들면 다음과 같습니다.
src/scss/objects/_box.scss
.o-box--spacing-disable-left, .o-box--spacing-horizontal { padding-left: 0; }
필터는 단일 클래스만 허용하므로 하나의 스타일 선언 블록이 둘 이상의 클래스에 대해 있을 수 있는 가능성을 고려하지 않습니다.
이를 설명하기 위해 filter()
믹스인을 확장하여 단일 클래스 외에도 많은 클래스를 포함하는 Sass arglist를 허용할 수 있습니다. 이렇게:
src/scss/objects/_box.scss
@include filter('o-box--spacing-disable-left', 'o-box--spacing-horizontal') { .o-box--spacing-disable-left, .o-box--spacing-horizontal { padding-left: 0; } }
따라서 이러한 클래스 중 하나가 $global-filter
에 있으면 클래스를 컴파일할 수 있음을 filter()
mixin에 알려야 합니다.
이것은 각 항목이 $global-filter
변수에 있는지 확인하기 위해 arglist가 전달되면 루프로 응답하여 mixin의 $class
인수를 유형 확인하는 추가 논리를 포함합니다.
src/scss/tools/_filter.scss
@mixin filter($class...) { @if(type-of($class) == 'arglist') { @each $item in $class { @if(index($global-filter, $item)) { @content; } } } @else if(index($global-filter, $class)) { @content; } }
그런 다음 filter()
mixin을 올바르게 적용하려면 다음 부분으로 돌아가기만 하면 됩니다.
-
objects/_box.scss
-
objects/_layout.scss
-
utilities/_alignments.scss
이 시점에서 $imports
로 돌아가 확장기 구성 요소만 활성화합니다. 컴파일된 스타일시트에서 일반 및 요소 레이어의 스타일 외에 다음 항목만 표시되어야 합니다.
- 확장기 구성 요소에 속하지만 수정자가 아닌 블록 및 요소 클래스.
- 확장기의 종속성에 속하는 블록 및 요소 클래스입니다.
-
$dependencies
변수에 명시적으로 선언된 확장기의 종속성에 속하는 모든 수정자 클래스.
이론적으로 확장자 구성 요소 수정자와 같이 컴파일된 스타일시트에 더 많은 클래스를 포함하기로 결정했다면 선언 시점에서 $global-filter
변수에 추가하거나 다른 시점에 추가하면 됩니다. 코드베이스에서 (수정자 자체가 선언된 지점 이전에 있는 한).
모든 것을 가능하게 하기
이제 우리는 개체, 구성 요소 및 유틸리티를 이러한 부분 내의 개별 클래스로 가져올 수 있는 매우 완전한 시스템을 갖게 되었습니다.
이유가 무엇이든 개발 중에 모든 것을 한 번에 활성화하고 싶을 수 있습니다. 이를 허용하기 위해 $enable-all-classes
라는 새 변수를 만든 다음 몇 가지 추가 논리를 추가하여 이것이 true로 설정되면 $imports
및 $global-filter
상태에 관계없이 모든 것이 컴파일됩니다. $global-filter
변수.
먼저 기본 매니페스트 파일에서 변수를 선언합니다.
src/scss/main.scss
$enable-all-classes: false; @import 'settings/core'; @import 'tools/core'; @import 'generic/core'; @import 'elements/core'; @import 'objects/core'; @import 'components/core'; @import 'utilities/core';
그런 다음 $enable-all-classes
변수가 true로 설정된 경우 재정의 논리를 추가하기 위해 filter()
및 render()
믹스인을 약간 수정하면 됩니다.
먼저 filter()
믹스인입니다. 기존 검사 전에 @if
문을 추가하여 $enable-all-classes
가 true로 설정되었는지 확인하고, 그렇다면 @content
를 렌더링하여 질문하지 않습니다.
src/scss/tools/_filter.scss
@mixin filter($class...) { @if($enable-all-classes) { @content; } @else if(type-of($class) == 'arglist') { @each $item in $class { @if(index($global-filter, $item)) { @content; } } } @else if(index($global-filter, $class)) { @content; } }
다음으로 render()
믹스인에서 우리는 $enable-all-classes
변수가 사실인지 확인하기 위해 검사를 수행하고, 그렇다면 추가 검사를 건너뛸 필요가 있습니다.
src/scss/tools/_render.scss
$layer: null !default; @mixin render($name) { @if($enable-all-classes or index(map-get($imports, $layer), $name)) { @content; } }
이제 $enable-all-classes
변수를 true로 설정하고 다시 빌드하면 모든 선택적 클래스가 컴파일되어 프로세스에서 상당한 시간을 절약할 수 있습니다.
비교
이 기술이 우리에게 어떤 유형의 이점을 제공하는지 확인하기 위해 몇 가지 비교를 실행하고 파일 크기 차이가 무엇인지 확인합니다.
비교가 공정한지 확인하려면 $imports
에 상자 및 컨테이너 개체를 추가한 다음 다음과 같이 상자의 o-box--spacing-regular
수정자를 $global-filter
에 추가해야 합니다.
src/scss/settings/_imports.scss
$imports: ( object: ( 'box', 'container' // 'layout' ), component: ( // 'button', 'expander' // 'typography' ), utility: ( // 'alignments', // 'widths' ) ); $global-filter: ( 'o-box--spacing-regular' );
이렇게 하면 확장기의 상위 요소에 대한 스타일이 필터링이 발생하지 않는 것처럼 컴파일됩니다.
원본 및 필터링된 스타일시트
모든 클래스가 컴파일된 원본 스타일시트와 확장 구성요소에 필요한 CSS만 컴파일된 필터링된 스타일시트를 비교해 보겠습니다.
기준 | ||
---|---|---|
스타일시트 | 크기(kb) | 사이즈(gzip) |
원래의 | 54.6kb | 6.98kb |
거르는 | 15.34kb(72% 더 작음) | 4.91kb(29% 더 작음) |
- 원본: https://webdevluke.github.io/handlingunusedcss/dist/index2.html
- 필터링됨: https://webdevluke.github.io/handlingunusedcss/dist/index.html
원본 스타일시트와 필터링된 스타일시트 사이에 큰 차이가 없기 때문에 gzip 백분율 절감은 이것이 노력할 가치가 없다는 것을 의미한다고 생각할 수 있습니다.
gzip 압축은 더 크고 반복적인 파일에서 더 잘 작동한다는 점을 강조할 가치가 있습니다. 필터링된 스타일시트는 유일한 개념 증명이며 확장기 구성 요소에 대한 CSS만 포함하기 때문에 실제 프로젝트에서만큼 압축할 것이 없습니다.
웹 사이트의 CSS 번들 크기보다 일반적인 크기로 각 스타일시트를 10배 확장하면 gzip 파일 크기의 차이가 훨씬 더 인상적입니다.
10배 크기 | ||
---|---|---|
스타일시트 | 크기(kb) | 사이즈(gzip) |
원본(10x) | 892.07kb | 75.70kb |
필터링됨(10x) | 209.45kb(77% 더 작음) | 19.47kb(74% 더 작음) |
필터링된 스타일시트 대 UNCSS
다음은 필터링된 스타일시트와 UNCSS 도구를 통해 실행된 스타일시트를 비교한 것입니다.
필터링된 대 UNCSS | ||
---|---|---|
스타일시트 | 크기(kb) | 사이즈(gzip) |
거르는 | 15.34kb | 4.91kb |
UNCSS | 12.89kb(16% 더 작음) | 4.25kb(13% 더 작음) |
UNCSS 도구는 일반 및 요소 디렉토리에서 CSS를 필터링하므로 여기에서 약간 유리합니다.
더 다양한 HTML 요소를 사용하는 실제 웹사이트에서 두 가지 방법의 차이는 무시할 수 있습니다.
마무리
그래서 우리는 Sass만 사용하여 빌드 시 컴파일되는 CSS 클래스를 더 잘 제어할 수 있는 방법을 보았습니다. 이렇게 하면 최종 스타일시트에서 사용되지 않는 CSS의 양이 줄어들고 중요한 렌더링 경로의 속도가 빨라집니다.
기사 시작 부분에서 UNCSS와 같은 기존 솔루션의 몇 가지 단점을 나열했습니다. 이 Sass 지향 솔루션을 같은 방식으로 비판하는 것은 공정하므로 어떤 접근 방식이 더 나은지 결정하기 전에 모든 사실이 테이블에 있습니다.
장점
- 추가 종속성이 필요하지 않으므로 다른 사람의 코드에 의존할 필요가 없습니다.
- 코드를 감사하기 위해 헤드리스 브라우저를 실행할 필요가 없으므로 Node.js 기반 대안보다 빌드 시간이 덜 필요합니다. 이는 빌드 대기열을 볼 가능성이 적을 수 있으므로 지속적인 통합에 특히 유용합니다.
- 자동화된 도구와 비교할 때 유사한 파일 크기가 나타납니다.
- 코드에서 해당 CSS 클래스가 사용되는 방식에 관계없이 기본적으로 필터링되는 코드를 완전히 제어할 수 있습니다. Node.js 기반 대안을 사용하면 동적으로 삽입된 HTML에 속하는 CSS 클래스가 필터링되지 않도록 별도의 화이트리스트를 유지해야 하는 경우가 많습니다.
단점
- Sass 지향 솔루션은
$imports
및$global-filter
변수를 계속 관리해야 한다는 점에서 확실히 더 실용적입니다. 초기 설정 외에도 우리가 살펴본 Node.js 대안은 대부분 자동화되어 있습니다. -
$global-filter
에 CSS 클래스를 추가한 다음 나중에 HTML에서 제거하는 경우 변수를 업데이트해야 한다는 것을 기억해야 합니다. 그렇지 않으면 필요하지 않은 CSS를 컴파일하게 됩니다. 한 번에 여러 개발자가 대규모 프로젝트를 진행하기 때문에 적절한 계획을 세우지 않으면 관리하기가 쉽지 않을 수 있습니다. - 의존성을 결합하고
render()
mixin을 많은 클래스에 적용하는 데 상당한 시간을 소비해야 하기 때문에 이 시스템을 기존 CSS 코드베이스에 연결하는 것은 권장하지 않습니다. 기존 코드가 없는 새 빌드로 구현하는 것이 훨씬 더 쉬운 시스템입니다.
내가 함께 모으는 것이 흥미로웠다는 것을 알게 된 것처럼 당신도 이 글을 읽는 것이 흥미로웠기를 바랍니다. 이 접근 방식을 개선하기 위한 제안, 아이디어가 있거나 내가 완전히 놓친 치명적인 결함을 지적하고 싶은 경우 아래 의견에 게시하십시오.