パフォーマンスを向上させるためにSassで未使用のCSSを処理する

公開: 2022-03-10
簡単な要約↬未使用のCSSがパフォーマンスに与える影響を知っていますか? ネタバレ:それはたくさんです! この記事では、未使用のCSSを処理するためのSass指向のソリューションについて説明し、ヘッドレスブラウザーやDOMエミュレーションを含む複雑なNode.jsの依存関係の必要性を回避します。

最新のフロントエンド開発では、開発者はスケーラブルで保守可能なCSSの作成を目指す必要があります。 そうしないと、コードベースが成長し、より多くの開発者が貢献するにつれて、カスケードやセレクターの特異性などの詳細を制御できなくなるリスクがあります。

これを実現する1つの方法は、オブジェクト指向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クラスを使用しています。 次の例は、Bootstrapを使用して構築された一般的なグリッドシステムを示しています。これは垂直方向にスタックし、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クラスの影響の測定

私は強力なブレードであるシェフィールドユナイテッドが大好きですが、彼らのWebサイトのCSSは単一の568kbの縮小ファイルにバンドルされており、gzipで圧縮しても105kbになります。 それはたくさんのようです。

これはシェフィールドユナイテッドのウェブサイト、私の地元のサッカーチームです(植民地であなたのためのサッカーです)。 (大プレビュー)

このCSSのどれだけが彼らのホームページで実際に使用されているか見てみましょう。 グーグルですばやく検索すると、仕事に至るまでのオンラインツールがたくさん見つかりますが、私はChromeのカバレッジツールを使用することを好みます。これは、ChromeのDevToolsから直接実行できます。 回転させましょう。

開発者ツールでカバレッジツールにアクセスする最も簡単な方法は、キーボードショートカットのControl + Shift + PまたはCommand + Shift + P(Mac)を使用してコマンドメニューを開くことです。 その中に、 coverageと入力し、[ShowCoverage]オプションを選択します。 (大プレビュー)

結果は、568kbのスタイルシートから30kbのCSSのみがホームページで使用され、残りの538kbは残りのWebサイトに必要なスタイルに関連していることを示しています。 これは、CSSのなんと94.8%が未使用であることを意味します。

Chromeのアセットのこのようなタイミングは、[ネットワーク]-> [アセット]-> [タイミング]タブを介してデベロッパーツールで確認できます。 (大プレビュー)

CSSは、Webページの重要なレンダリングパスの一部です。これには、ブラウザがページレンダリングを開始する前に完了する必要のあるさまざまな手順がすべて含まれます。 これにより、CSSはレンダリングをブロックするアセットになります。

したがって、これを念頭に置いて、良好な3G接続を使用してシェフィールドユナイテッドのWebサイトをロードする場合、CSSがダウンロードされてページレンダリングを開始できるようになるまでに1.15秒かかります。 これは問題です。

Googleもこれを認識しています。 オンラインまたはブラウザを介してLighthouse監査を実行すると、未使用のCSSを削除することでロード時間とファイルサイズを節約できる可能性が強調表示されます。

Chrome(およびChromium Edge)では、開発者ツールの[監査]タブをクリックして、GoogleLighthouseの監査を正しく行うことができます。 (大プレビュー)

既存のソリューション

目標は、不要なCSSクラスを判別し、それらをスタイルシートから削除することです。 このプロセスを自動化しようとする既存のソリューションが利用可能です。 これらは通常、Node.jsビルドスクリプトを介して、またはGulpなどのタスクランナーを介して使用できます。 これらには以下が含まれます:

  • UNCSS
  • PurifyCSS
  • PurgeCSS

これらは通常、同様の方法で機能します。

  1. Bulldでは、ヘッドレスブラウザ(例:puppeteer)またはDOMエミュレーション(例:jsdom)を介してWebサイトにアクセスします。
  2. ページのHTML要素に基づいて、未使用のCSSが識別されます。
  3. これはスタイルシートから削除され、必要なものだけが残ります。

これらの自動化されたツールは完全に有効であり、多くの商用プロジェクトでそれらの多くをうまく使用してきましたが、途中で共有する価値のあるいくつかの欠点に遭遇しました。

  • クラス名に「@」や「/」などの特殊文字が含まれている場合、カスタムコードを記述しないとこれらの文字が認識されない場合があります。 私はHarryRobertsのBEM-ITを使用しています。これには、 u-width-6/12@lgようなレスポンシブサフィックスを使用してクラス名を構造化することが含まれるため、以前にこの問題が発生しました。
  • Webサイトが自動展開を使用している場合、特にページとCSSが多数ある場合は、ビルドプロセスが遅くなる可能性があります。
  • これらのツールに関する知識はチーム全体で共有する必要があります。そうしないと、CSSがプロダクションスタイルシートに不思議に欠けているときに混乱やフラストレーションが生じる可能性があります。
  • Webサイトで多くのサードパーティのスクリプトが実行されている場合、ヘッドレスブラウザで開いたときに、これらのスクリプトがうまく機能せず、フィルタリングプロセスでエラーが発生する可能性があります。 そのため、通常、ヘッドレスブラウザが検出されたときにサードパーティのスクリプトを除外するカスタムコードを作成する必要があります。これは、設定によっては注意が必要な場合があります。
  • 一般に、これらの種類のツールは複雑であり、ビルドプロセスに多くの追加の依存関係をもたらします。 すべてのサードパーティの依存関係の場合と同様に、これは他の誰かのコードに依存することを意味します。

これらの点を念頭に置いて、私は自分自身に質問を投げかけました:

Sassだけを使用して、コンパイルしたSassをより適切に処理して、Sassのソースクラスを完全に削除することなく、未使用のCSSを除外できるようにすることは可能ですか?

ネタバレ注意:答えはイエスです。 これが私が思いついたものです。

Sass指向のソリューション

このソリューションは、Sassをコンパイルする必要があるものをすばやく簡単に選択する方法を提供する必要がありますが、開発プロセスをさらに複雑にしたり、開発者がプロ​​グラムで生成されたCSSなどを利用することを妨げたりしないほどシンプルです。クラス。

開始するには、ビルドスクリプトといくつかのサンプルスタイルを含むリポジトリがあり、ここから複製できます。

ヒント:行き詰まった場合は、マスターブランチで完成したバージョンといつでも相互参照できます。

リポジトリにcdし、 npm installを実行してから、 npm run buildを実行して、必要に応じてSassをCSSにコンパイルします。 これにより、distディレクトリに55kbのcssファイルが作成されます。

次に、Webブラウザで/dist/index.htmlを開くと、かなり標準的なコンポーネントが表示されます。このコンポーネントをクリックすると展開され、コンテンツが表示されます。 実際のネットワーク条件が適用されるここでこれを表示することもできるため、独自のテストを実行できます。

未使用のCSSを処理するためのSass指向のソリューションを開発する際のテスト対象として、このエクスパンダーUIコンポーネントを使用します。 (大プレビュー)

パーシャルレベルでのフィルタリング

通常のSCSSセットアップでは、単一のマニフェストファイル(例:リポジトリ内のmain.scss )、またはページごとに1つ(例: index.scssproducts.scsscontact.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を調べてこれらの関係を認識していない限り、これらのパーシャルをインポートすることを知らない可能性があるため、必要なクラスのすべてがコンパイルされるわけではありません。

リポジトリで、 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の標準セットのように動作する必要があります。つまり、特定のパーシャルがコメントアウトされている場合(上記のように)、そのコードはビルド時にコンパイルされるべきではありません。

ただし、依存関係を自動的にインポートしたいので、適切な状況ではこのマップを無視できるはずです。

Mixinをレンダリングする

Sassロジックの追加を始めましょう。 src/scss/tools_render.scssを作成し、その@importtools/_core.scssに追加します。

ファイルに、 render()という空のミックスインを作成します。

src/scss/tools/_render.scss

 @mixin render() { }

ミックスインでは、次のことを行うSassを作成する必要があります。

  • 与える()
    「ねえ、 $importsがあります、天気がいいですね。 たとえば、マップにコンテナオブジェクトがありますか?」
  • $ imports
    false
  • 与える()
    「それは残念です。そのときはコンパイルされないようです。 ボタンコンポーネントはどうですか?」
  • $ imports
    true
  • 与える()
    "良い! それがコンパイルされているボタンです。 私のために妻に挨拶してください。」

Sassでは、これは次のように変換されます。

src/scss/tools/_render.scss

 @mixin render($name, $layer) { @if(index(map-get($imports, $layer), $name)) { @content; } }

基本的に、パーシャルが$imports変数に含まれているかどうかを確認し、含まれている場合は、Sassの@contentディレクティブを使用してレンダリングします。これにより、コンテンツブロックをミックスインに渡すことができます。

私たちはそれを次のように使用します:

例5

 @include render('button', 'component') { .c-button { // styles et al } // any other class declarations }

このミックスインを使用する前に、少し改善することができます。 レイヤー名(オブジェクト、コンポーネント、ユーティリティなど)は安全に予測できるものなので、少し合理化する機会があります。

レンダーミックスイン宣言の前に、 $layerという変数を作成し、同じ名前の変数をミックスインパラメーターから削除します。 そのようです:

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()ミックスインを各オブジェクト、コンポーネント、およびユーティリティクラス宣言の周りにラップします。 これにより、パーシャルごとに1つのレンダーミックスインが使用されます。

例えば:

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の場合、 render()関数をパーシャル全体にラップすると、コンパイル時にエラーが発生します。Sassの場合、ミックスイン宣言をミックスイン呼び出し内にネストすることはできません。 代わりに、以下のように、 render()ミックスインをcreate-widths()呼び出しにラップします。

 @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 import it in 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 distributionsの下に、 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は、ミックスインのローカルスコープではなく、グローバルスコープで$imports変数を検索するようになります。

4.次に、ミックスインを呼び出すだけです。

 @mixin dependency-setup() { ... } @include dependency-setup();

つまり、現在あるのは拡張部分インポートシステムです。このシステムでは、コンポーネントがインポートされた場合、開発者はさまざまな依存関係の部分を手動でインポートする必要もありません。

$imports変数を構成して、エキスパンダーコンポーネントのみがインポートされるようにしてから、 npm run buildます。 コンパイルされたCSSに、エクスパンダクラスとそのすべての依存関係が表示されます。

ただし、プログラムであるかどうかに関係なく、同じ量のSassがまだインポートされているため、未使用のCSSを除外するという点で、これによってテーブルに新しいものがもたらされることはありません。 これを改善しましょう。

依存関係のインポートの改善

コンポーネントが依存関係から1つのクラスのみを必要とする場合があるため、次に進み、その依存関係のすべてのクラスをインポートすると、回避しようとしているのと同じ不要な肥大化につながります。

システムを改良して、クラスごとのよりきめ細かいフィルタリングを可能にし、コンポーネントが必要な依存関係クラスのみでコンパイルされるようにすることができます。

ほとんどのデザインパターンでは、装飾されているかどうかに関係なく、パターンを正しく表示するためにスタイルシートに存在する必要のあるクラスが最小限に抑えられています。

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; }

フィルタは単一のクラスのみを受け入れるため、1つのスタイル宣言ブロックが複数のクラスに対応している可能性は考慮されていません。

これを説明するために、 filter()ミックスインを拡張して、単一のクラスに加えて、多くのクラスを含むSassarglistを受け入れることができるようにします。 そのようです:

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()ミックスインに通知する必要があります。

これには、ミックスインの$class引数を型チェックするための追加のロジックが含まれ、各アイテムが$global-filter変数にあるかどうかをチェックするためにarglistが渡された場合にループで応答します。

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()ミックスインを正しく適用するだけです。

  • objects/_box.scss
  • objects/_layout.scss
  • utilities/_alignments.scss

この時点で、 $importsに戻り、エキスパンダーコンポーネントのみを有効にします。 コンパイルされたスタイルシートでは、genericレイヤーとelementsレイヤーのスタイルに加えて、次のように表示されるだけです。

  • エキスパンダーコンポーネントに属するが、そのモディファイアには属さないブロッククラスと要素クラス。
  • エクスパンダの依存関係に属するブロッククラスと要素クラス。
  • $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';

次に、 filter()render()のミックスインを少し編集して、 $enable-all-classes変数がtrueに設定されている場合のオーバーライドロジックを追加する必要があります。

まず、 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のみが含まれているため、実際のプロジェクトほど圧縮する必要はありません。

各スタイルシートを10倍に拡大して、WebサイトのCSSバンドルサイズに典型的なサイズにすると、gzipファイルサイズの違いははるかに印象的です。

10倍サイズ
スタイルシートサイズ(kb) サイズ(gzip)
オリジナル(10倍) 892.07kb 75.70kb
フィルター済み(10x) 209.45kb(77%小さい) 19.47kb(74%小さい)

フィルタリングされたスタイルシートとUNCSS

これは、フィルター処理されたスタイルシートとUNCSSツールを介して実行されたスタイルシートの比較です。

フィルター処理vsUNCSS
スタイルシートサイズ(kb) サイズ(gzip)
フィルタリング15.34kb 4.91kb
UNCSS 12.89kb(16%小さい) 4.25kb(13%小さい)

UNCSSツールは、genericディレクトリとelementsディレクトリのCSSを除外しているため、ここではわずかに勝ちます。

さまざまなHTML要素が使用されている実際のWebサイトでは、2つの方法の違いはごくわずかである可能性があります。

まとめ

つまり、Sassだけを使用して、ビルド時にコンパイルされるCSSクラスをより細かく制御できることを確認しました。 これにより、最終的なスタイルシートの未使用のCSSの量が減り、重要なレンダリングパスが高速化されます。

記事の冒頭で、UNCSSなどの既存のソリューションのいくつかの欠点をリストしました。 このSass指向のソリューションを同じように批評するのは公正なことなので、どちらのアプローチが自分に適しているかを判断する前に、すべての事実がテーブルにあります。

長所

  • 追加の依存関係は必要ないので、他の誰かのコードに依存する必要はありません。
  • コードを監査するためにヘッドレスブラウザーを実行する必要がないため、Node.jsベースの代替手段よりも必要なビルド時間が短くなります。 これは、ビルドのキューが表示される可能性が低いため、継続的インテグレーションで特に役立ちます。
  • 自動化されたツールと比較した場合、同様のファイルサイズになります。
  • 箱から出して、それらのCSSクラスがコードでどのように使用されているかに関係なく、フィルタリングされるコードを完全に制御できます。 Node.jsベースの代替手段では、動的に挿入されたHTMLに属するCSSクラスが除外されないように、個別のホワイトリストを維持する必要があることがよくあります。

短所

  • Sass指向のソリューションは、 $imports変数と$global-filter変数を常に把握している必要があるという意味で、間違いなくより実践的なものです。 初期設定以外に、これまで見てきたNode.jsの代替案は大部分が自動化されています。
  • CSSクラスを$global-filterに追加し、後でHTMLから削除する場合は、変数を更新することを忘れないでください。そうしないと、不要なCSSをコンパイルすることになります。 大規模なプロジェクトが一度に複数の開発者によって作業されているため、適切に計画しない限り、これを管理するのは簡単ではない可能性があります。
  • このシステムを既存のCSSコードベースにボルトで固定することはお勧めしません。依存関係をつなぎ合わせて、 render()ミックスインを多数のクラスに適用するのにかなりの時間を費やす必要があるからです。 これは、競合する既存のコードがない新しいビルドで実装するのがはるかに簡単なシステムです。

うまくいけば、私がまとめるのが面白いと思ったのと同じくらい、これを読むのが面白いと思ったでしょう。 このアプローチを改善するための提案やアイデアがある場合、または私が完全に見逃した致命的な欠陥を指摘したい場合は、以下のコメントに投稿してください。