Menangani CSS yang Tidak Digunakan Di Sass Untuk Meningkatkan Kinerja
Diterbitkan: 2022-03-10Dalam pengembangan front-end modern, pengembang harus bertujuan untuk menulis CSS yang terukur dan dapat dipelihara. Jika tidak, mereka berisiko kehilangan kendali atas hal-hal spesifik seperti kaskade dan kekhususan pemilih saat basis kode tumbuh dan lebih banyak pengembang berkontribusi.
Salah satu cara ini dapat dicapai adalah melalui penggunaan metodologi seperti CSS Berorientasi Objek (OOCSS), yang alih-alih mengatur CSS di sekitar konteks halaman, mendorong pemisahan struktur (sistem kisi, spasi, lebar, dll.) dari dekorasi (font, merek, warna, dll).
Jadi nama kelas CSS seperti:
-
.blog-right-column
-
.latest_topics_list
-
.job-vacancy-ad
Diganti dengan alternatif yang lebih dapat digunakan kembali, yang menerapkan gaya CSS yang sama, tetapi tidak terikat pada konteks tertentu:
-
.col-md-4
-
.list-group
-
.card
Pendekatan ini biasanya diimplementasikan dengan bantuan kerangka kerja Sass seperti Bootstrap, Foundation, atau semakin sering, kerangka kerja dipesan lebih dahulu yang dapat dibentuk agar lebih sesuai dengan proyek.
Jadi sekarang kami menggunakan kelas CSS yang dipilih dari kerangka pola, komponen UI, dan kelas utilitas. Contoh di bawah ini mengilustrasikan sistem grid umum yang dibangun menggunakan Bootstrap, yang menumpuk secara vertikal, kemudian setelah breakpoint md tercapai, beralih ke tata letak 3 kolom.
<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>
Kelas yang dihasilkan secara terprogram seperti .col-12
dan .col-md-4
digunakan di sini untuk membuat pola ini. Tapi bagaimana dengan .col-1
sampai .col-11
, .col-lg-4
, .col-md-6
atau .col-sm-12
? Ini semua adalah contoh kelas yang akan disertakan dalam stylesheet CSS yang dikompilasi, diunduh dan diurai oleh browser, meskipun tidak sedang digunakan.
Pada artikel ini, kita akan mulai dengan menjelajahi dampak CSS yang tidak digunakan pada kecepatan pemuatan halaman. Kami kemudian akan menyentuh beberapa solusi yang ada untuk menghapusnya dari stylesheet, menindaklanjuti dengan solusi berorientasi Sass saya sendiri.
Mengukur Dampak Kelas CSS yang Tidak Digunakan
Sementara saya menyukai Sheffield United, pisau perkasa, CSS situs web mereka dibundel menjadi satu file kecil berukuran 568kb, yang menjadi 105kb bahkan ketika di-gzip. Itu sepertinya banyak.
Bisakah kita melihat berapa banyak CSS ini yang sebenarnya digunakan di beranda mereka? Pencarian Google cepat mengungkapkan banyak alat online untuk pekerjaan itu, tetapi saya lebih suka menggunakan alat cakupan di Chrome, yang dapat dijalankan langsung dari DevTools Chrome. Mari kita putar.
Hasilnya menunjukkan bahwa hanya 30kb CSS dari 568kb stylesheet yang digunakan oleh beranda, dengan 538kb sisanya terkait dengan gaya yang diperlukan untuk sisa situs web. Ini berarti 94,8% dari CSS tidak digunakan.
CSS adalah bagian dari jalur rendering penting halaman web, yang melibatkan semua langkah berbeda yang harus diselesaikan browser sebelum dapat memulai rendering halaman. Ini menjadikan CSS sebagai aset yang memblokir render.
Jadi dengan mengingat hal ini, saat memuat situs web Sheffield United menggunakan koneksi 3G yang baik, dibutuhkan waktu 1,15 detik sebelum CSS diunduh dan rendering halaman dapat dimulai. Ini adalah sebuah masalah.
Google telah mengakui ini juga. Saat menjalankan audit Lighthouse, online atau melalui browser Anda, setiap potensi waktu muat dan penghematan ukuran file yang dapat dilakukan dengan menghapus CSS yang tidak digunakan akan disorot.
Solusi yang ada
Tujuannya adalah untuk menentukan kelas CSS mana yang tidak diperlukan dan menghapusnya dari stylesheet. Solusi yang ada tersedia yang mencoba untuk mengotomatisasi proses ini. Mereka biasanya dapat digunakan melalui skrip pembuatan Node.js, atau melalui pelari tugas seperti Gulp. Ini termasuk:
- UNCSS
- PurifyCSS
- BersihkanCSS
Ini umumnya bekerja dengan cara yang serupa:
- Di bulld, situs web diakses melalui browser tanpa kepala (Misalnya: dalang) atau emulasi DOM (Misalnya: jsdom).
- Berdasarkan elemen HTML halaman, setiap CSS yang tidak digunakan akan diidentifikasi.
- Ini dihapus dari stylesheet, hanya menyisakan apa yang dibutuhkan.
Sementara alat otomatis ini benar-benar valid dan saya telah menggunakan banyak dari mereka di sejumlah proyek komersial dengan sukses, saya telah menemukan beberapa kelemahan di sepanjang jalan yang layak untuk dibagikan:
- Jika nama kelas berisi karakter khusus seperti '@' atau '/', ini mungkin tidak dikenali tanpa menulis beberapa kode khusus. Saya menggunakan BEM-IT oleh Harry Roberts, yang melibatkan penataan nama kelas dengan sufiks responsif seperti:
u-width-6/12@lg
, jadi saya telah menemukan masalah ini sebelumnya. - Jika situs web menggunakan penerapan otomatis, itu dapat memperlambat proses pembuatan, terutama jika Anda memiliki banyak halaman dan banyak CSS.
- Pengetahuan tentang alat-alat ini perlu dibagikan ke seluruh tim, jika tidak, mungkin ada kebingungan dan frustrasi ketika CSS secara misterius tidak ada dalam lembar gaya produksi.
- Jika situs web Anda memiliki banyak skrip pihak ke-3 yang berjalan, terkadang saat dibuka di browser tanpa kepala, skrip ini tidak berfungsi dengan baik dan dapat menyebabkan kesalahan pada proses pemfilteran. Jadi biasanya Anda harus menulis kode khusus untuk mengecualikan skrip pihak ketiga mana pun saat browser tanpa kepala terdeteksi, yang bergantung pada pengaturan Anda, mungkin rumit.
- Umumnya, alat semacam ini rumit dan memperkenalkan banyak ketergantungan ekstra pada proses pembuatan. Seperti halnya dengan semua dependensi pihak ketiga, ini berarti mengandalkan kode orang lain.
Dengan mengingat poin-poin ini, saya mengajukan pertanyaan kepada diri sendiri:
Hanya dengan menggunakan Sass, apakah mungkin untuk menangani Sass yang kami kompilasi dengan lebih baik sehingga CSS yang tidak digunakan dapat dikecualikan, tanpa harus menghapus secara kasar kelas sumber di Sass secara langsung?
Peringatan spoiler: Jawabannya adalah ya. Inilah yang saya dapatkan.
Solusi Berorientasi Sass
Solusinya perlu menyediakan cara cepat dan mudah untuk memilih apa yang harus dikompilasi Sass, sementara cukup sederhana sehingga tidak menambah kerumitan lagi pada proses pengembangan atau mencegah pengembang mengambil keuntungan dari hal-hal seperti CSS yang dihasilkan secara terprogram kelas.
Untuk memulai, ada repo dengan skrip build dan beberapa contoh gaya yang dapat Anda tiru dari sini.
Tip: Jika Anda buntu, Anda selalu dapat melakukan referensi silang dengan versi yang telah selesai di cabang master.
cd
ke dalam repo, jalankan npm install
dan kemudian npm run build
untuk mengkompilasi Sass apa pun ke dalam CSS sesuai kebutuhan. Ini harus membuat file css 55kb di direktori dist.
Jika Anda kemudian membuka /dist/index.html
di browser web Anda, Anda akan melihat komponen yang cukup standar, yang jika diklik akan meluas untuk menampilkan beberapa konten. Anda juga dapat melihat ini di sini, di mana kondisi jaringan nyata akan diterapkan, sehingga Anda dapat menjalankan pengujian Anda sendiri.
Penyaringan Pada Tingkat Parsial
Dalam pengaturan SCSS biasa, Anda mungkin akan memiliki satu file manifes (mis: main.scss
dalam repo), atau satu per halaman (mis: index.scss
, products.scss
, contact.scss
) di mana sebagian kerangka kerja diimpor. Mengikuti prinsip OOCSS, impor tersebut mungkin terlihat seperti ini:
Contoh 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';
Jika salah satu dari sebagian ini tidak digunakan, maka cara alami untuk memfilter CSS yang tidak digunakan ini adalah dengan menonaktifkan impor, yang akan mencegahnya dikompilasi.
Misalnya, jika hanya menggunakan komponen expander, manifes biasanya akan terlihat seperti di bawah ini:
Contoh 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';
Namun, sesuai OOCSS, kami memisahkan dekorasi dari struktur untuk memungkinkan penggunaan kembali yang maksimal, jadi mungkin saja expander memerlukan CSS dari objek, komponen, atau kelas utilitas lain untuk dirender dengan benar. Kecuali jika pengembang mengetahui hubungan ini dengan memeriksa HTML, mereka mungkin tidak tahu untuk mengimpor sebagian ini, jadi tidak semua kelas yang diperlukan akan dikompilasi.
Dalam repo, jika Anda melihat HTML expander di dist/index.html
, tampaknya memang demikian. Ini menggunakan gaya dari kotak dan objek tata letak, komponen tipografi, dan utilitas lebar dan perataan.
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>
Mari kita atasi masalah ini yang menunggu untuk terjadi dengan membuat hubungan ini resmi di dalam Sass itu sendiri, jadi setelah komponen diimpor, semua dependensi juga akan diimpor secara otomatis. Dengan cara ini, pengembang tidak lagi memiliki biaya tambahan karena harus mengaudit HTML untuk mempelajari apa lagi yang perlu mereka impor.
Peta Impor Terprogram
Agar sistem ketergantungan ini berfungsi, daripada hanya mengomentari pernyataan @import
dalam file manifes, logika Sass perlu menentukan apakah sebagian akan dikompilasi atau tidak.
Di src/scss/settings
, buat parsial baru bernama _imports.scss
, @import
di settings/_core.scss
, lalu buat peta SCSS berikut:
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' ) );
Peta ini akan memiliki peran yang sama dengan manifes impor kembali pada contoh 1.
Contoh 4
$imports: ( object: ( //'box', //'container', //'layout' ), component: ( //'button', 'expander', //'typography' ), utility: ( //'alignments', //'widths' ) );
Itu harus berperilaku seperti set standar @imports
, jika sebagian tertentu dikomentari (seperti di atas), maka kode itu tidak boleh dikompilasi pada build.
Tetapi karena kita ingin mengimpor dependensi secara otomatis, kita juga harus dapat mengabaikan peta ini dalam situasi yang tepat.
Render Mixin
Mari kita mulai menambahkan beberapa logika Sass. Buat _render.scss
di src/scss/tools
, lalu tambahkan @import
ke tools/_core.scss
.
Dalam file tersebut, buat mixin kosong bernama render()
.
src/scss/tools/_render.scss
@mixin render() { }
Dalam mixin, kita perlu menulis Sass yang melakukan hal berikut:
- memberikan()
“Hei disana$imports
, cuaca cerah bukan? Katakanlah, apakah Anda memiliki objek kontainer di peta Anda?" - $impor
false
- memberikan()
“Sayang sekali, sepertinya itu tidak akan dikompilasi. Bagaimana dengan komponen tombolnya?” - $impor
true
- memberikan()
"Bagus! Itu tombol yang sedang dikompilasi. Sampaikan salamku pada istriku.”
Di Sass, ini diterjemahkan sebagai berikut:
src/scss/tools/_render.scss
@mixin render($name, $layer) { @if(index(map-get($imports, $layer), $name)) { @content; } }
Pada dasarnya, periksa apakah parsial disertakan dalam variabel $imports
, dan jika demikian, render menggunakan arahan @content
Sass, yang memungkinkan kita untuk meneruskan blok konten ke dalam mixin.
Kami akan menggunakannya seperti ini:
Contoh 5
@include render('button', 'component') { .c-button { // styles et al } // any other class declarations }
Sebelum menggunakan mixin ini, ada sedikit perbaikan yang bisa kita lakukan. Nama lapisan (objek, komponen, utilitas, dll.) adalah sesuatu yang dapat kami prediksi dengan aman, jadi kami memiliki kesempatan untuk merampingkan hal-hal sedikit.
Sebelum deklarasi render mixin, buat variabel bernama $layer
, dan hapus variabel bernama identik dari parameter mixin. Seperti:
src/scss/tools/_render.scss
$layer: null !default; @mixin render($name) { @if(index(map-get($imports, $layer), $name)) { @content; } }
Sekarang, di bagian _core.scss
tempat objek, komponen, dan utilitas @imports
berada, nyatakan ulang variabel ini ke nilai berikut; mewakili jenis kelas CSS yang diimpor.
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';
Dengan cara ini, ketika kita menggunakan mixin render()
, yang harus kita lakukan adalah mendeklarasikan nama parsial.
Bungkus mixin render()
di sekitar setiap deklarasi objek, komponen, dan kelas utilitas, seperti di bawah ini. Ini akan memberi Anda satu penggunaan mixin render per parsial.
Sebagai contoh:
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 }
Catatan: Untuk utilities/_widths.scss
, membungkus fungsi render()
di sekitar seluruh parsial akan membuat kesalahan pada kompilasi, karena di Sass Anda tidak dapat membuat deklarasi mixin di dalam panggilan mixin. Sebagai gantinya, cukup bungkus mixin render()
di sekitar panggilan create-widths()
, seperti di bawah ini:
@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 }
Dengan ini, pada build, hanya sebagian yang direferensikan dalam $imports
yang akan dikompilasi.
Campur dan cocokkan komponen apa yang dikomentari di $imports
dan jalankan npm run build
di terminal untuk mencobanya.
Peta Ketergantungan
Sekarang kita secara terprogram mengimpor sebagian, kita dapat mulai menerapkan logika ketergantungan.
Di src/scss/settings
, buat parsial baru bernama _dependencies.scss
, @import
di settings/_core.scss
, tapi pastikan setelah _imports.scss
. Kemudian di dalamnya, buat peta SCSS berikut:
src/scss/settings/_dependencies.scss
$dependencies: ( expander: ( object: ( 'box', 'layout' ), component: ( 'button', 'typography' ), utility: ( 'alignments', 'widths' ) ) );
Di sini, kami mendeklarasikan dependensi untuk komponen expander karena memerlukan gaya dari parsial lain untuk dirender dengan benar, seperti yang terlihat di dist/index.html.
Dengan menggunakan daftar ini, kita dapat menulis logika yang berarti dependensi ini akan selalu dikompilasi bersama dengan komponen dependennya, tidak peduli status variabel $imports
.
Di bawah $dependencies
, buat sebuah mixin bernama dependency-setup()
. Di sini, kami akan melakukan tindakan berikut:
1. Ulangi peta dependensi.
@mixin dependency-setup() { @each $componentKey, $componentValue in $dependencies { } }
2. Jika komponen dapat ditemukan di $imports
, ulangi daftar dependensinya.
@mixin dependency-setup() { $components: map-get($imports, component); @each $componentKey, $componentValue in $dependencies { @if(index($components, $componentKey)) { @each $layerKey, $layerValue in $componentValue { } } } }
3. Jika dependensi tidak ada di $imports
, tambahkan.
@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; } } } } } }
Menyertakan flag !global
memberi tahu Sass untuk mencari variabel $imports
dalam lingkup global, daripada lingkup lokal mixin.
4. Maka itu hanya masalah memanggil mixin.
@mixin dependency-setup() { ... } @include dependency-setup();
Jadi apa yang kita miliki sekarang adalah sistem impor parsial yang disempurnakan, di mana jika komponen diimpor, pengembang tidak perlu mengimpor secara manual masing-masing dari berbagai parsial ketergantungannya juga.
Konfigurasikan variabel $imports
sehingga hanya komponen expander yang diimpor dan kemudian jalankan npm run build
. Anda akan melihat dalam CSS yang dikompilasi kelas expander bersama dengan semua dependensinya.
Namun, ini tidak benar-benar membawa sesuatu yang baru ke dalam tabel dalam hal menyaring CSS yang tidak digunakan, karena jumlah Sass yang sama masih diimpor, terprogram atau tidak. Mari kita tingkatkan ini.
Peningkatan Ketergantungan Mengimpor
Sebuah komponen mungkin hanya memerlukan satu kelas dari dependensi, jadi untuk kemudian melanjutkan dan mengimpor semua kelas dependensi itu hanya akan mengarah ke pengasapan yang tidak perlu yang kami coba hindari.
Kami dapat menyempurnakan sistem untuk memungkinkan pemfilteran yang lebih terperinci berdasarkan kelas per kelas, untuk memastikan komponen dikompilasi hanya dengan kelas dependensi yang mereka butuhkan.
Dengan sebagian besar pola desain, didekorasi atau tidak, terdapat jumlah minimum kelas yang perlu ada di lembar gaya agar pola dapat ditampilkan dengan benar.
Untuk nama kelas yang menggunakan konvensi penamaan yang mapan seperti BEM, biasanya kelas bernama "Blok" dan "Elemen" diperlukan sebagai minimum, dengan "Pengubah" biasanya opsional.
Catatan: Kelas utilitas biasanya tidak mengikuti rute BEM, karena mereka terisolasi di alam karena fokusnya yang sempit.
Misalnya, lihat objek media ini, yang mungkin merupakan contoh CSS berorientasi objek yang paling terkenal:
<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>
Jika komponen memiliki set ini sebagai dependensi, masuk akal untuk selalu mengkompilasi .o-media
, .o-media__image
dan .o-media__text
, karena itulah jumlah minimum CSS yang diperlukan untuk membuat pola bekerja. Namun dengan .o-media--spacing-small
sebagai pengubah opsional, itu seharusnya hanya dikompilasi jika kita secara eksplisit mengatakannya, karena penggunaannya mungkin tidak konsisten di semua instance objek media.
Kami akan memodifikasi struktur peta $dependencies
untuk memungkinkan kami mengimpor kelas opsional ini, sementara menyertakan cara untuk mengimpor hanya blok dan elemen jika tidak diperlukan pengubah.
Untuk memulai, periksa HTML expander di dist/index.html dan catat setiap kelas ketergantungan yang digunakan. Catat ini di peta $dependencies
, seperti di bawah ini:
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' ) ) ) );
Jika nilai disetel ke true, kami akan menerjemahkannya menjadi "Hanya kompilasi kelas blok dan level elemen, tanpa pengubah!".
Langkah selanjutnya melibatkan pembuatan variabel daftar putih untuk menyimpan kelas-kelas ini, dan kelas lain (non-ketergantungan) yang ingin kita impor secara manual. Di /src/scss/settings/imports.scss
, setelah $imports
, buat daftar Sass baru bernama $global-filter
.
src/scss/settings/_imports.scss
$global-filter: ();
Premis dasar di balik $global-filter
adalah bahwa setiap kelas yang disimpan di sini akan dikompilasi pada build selama sebagian dari mereka diimpor melalui $imports
.
Nama kelas ini dapat ditambahkan secara terprogram jika merupakan dependensi komponen, atau dapat ditambahkan secara manual saat variabel dideklarasikan, seperti pada contoh di bawah ini:
Contoh filter global
$global-filter: ( 'o-box--spacing-regular@md', 'u-align-center', 'u-width-6/12@lg' );
Selanjutnya, kita perlu menambahkan sedikit lebih banyak logika ke mixin @dependency-setup
, sehingga setiap kelas yang direferensikan dalam $dependencies
secara otomatis ditambahkan ke daftar putih $global-filter
kita.
Di bawah blok ini:
src/scss/settings/_dependencies.scss
@if not index(map-get($imports, $layerKey), $partKey) { }
...tambahkan cuplikan berikut.
src/scss/settings/_dependencies.scss
@each $class in $partValue { $global-filter: append($global-filter, '#{$class}', 'comma') !global; }
Ini mengulang semua kelas ketergantungan dan menambahkannya ke daftar putih $global-filter
.
Pada titik ini, jika Anda menambahkan pernyataan @debug
di bawah mixin dependency-setup()
untuk mencetak konten $global-filter
di terminal:
@debug $global-filter;
...Anda akan melihat sesuatu seperti ini di build:
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"
Sekarang kita memiliki daftar putih kelas, kita perlu menerapkan ini di semua objek, komponen, dan sebagian utilitas yang berbeda.
Buat parsial baru bernama _filter.scss
di src/scss/tools
dan tambahkan _core.scss
@import
alat.
Di bagian baru ini, kita akan membuat mixin bernama filter()
. Kami akan menggunakan ini untuk menerapkan logika yang berarti kelas hanya akan dikompilasi jika disertakan dalam variabel $global-filter
.
Mulai dari yang sederhana, buat mixin yang menerima satu parameter — $class
yang dikontrol oleh filter. Selanjutnya, jika $class
disertakan dalam daftar putih $global-filter
, izinkan untuk dikompilasi.
src/scss/tools/_filter.scss
@mixin filter($class) { @if(index($global-filter, $class)) { @content; } }
Secara parsial, kami akan membungkus mixin di sekitar kelas opsional, seperti:
@include filter('o-myobject--modifier') { .o-myobject--modifier { color: yellow; } }
Ini berarti kelas .o-myobject--modifier
hanya akan dikompilasi jika termasuk dalam $global-filter
, yang dapat diatur secara langsung, atau tidak langsung melalui apa yang diatur dalam $dependencies
.
Buka repo dan terapkan mixin filter()
ke semua kelas pengubah opsional di seluruh lapisan objek dan komponen. Saat menangani komponen tipografi atau lapisan utilitas, karena setiap kelas independen dari yang berikutnya, masuk akal untuk menjadikan semuanya opsional, jadi kami kemudian dapat mengaktifkan kelas saat kami membutuhkannya.
Berikut beberapa contohnya:
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; } } } }
Catatan: Saat menambahkan nama kelas sufiks responsif ke mixin filter()
, Anda tidak perlu keluar dari simbol '@' dengan '\'.
Selama proses ini, saat menerapkan mixin filter()
ke sebagian, Anda mungkin (atau mungkin tidak) memperhatikan beberapa hal.
Kelas yang Dikelompokkan
Beberapa kelas dalam basis kode dikelompokkan bersama dan memiliki gaya yang sama, misalnya:
src/scss/objects/_box.scss
.o-box--spacing-disable-left, .o-box--spacing-horizontal { padding-left: 0; }
Karena filter hanya menerima satu kelas, itu tidak memperhitungkan kemungkinan bahwa satu blok deklarasi gaya mungkin untuk lebih dari satu kelas.
Untuk menjelaskan hal ini, kami akan memperluas mixin filter()
sehingga selain satu kelas, ini dapat menerima arglist Sass yang berisi banyak kelas. Seperti:
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; } }
Jadi kita perlu memberi tahu mixin filter()
bahwa jika salah satu dari kelas ini ada di $global-filter
, Anda diizinkan untuk mengompilasi kelas.
Ini akan melibatkan logika tambahan untuk mengetik check argumen $class
mixin, merespons dengan loop jika arglist dilewatkan untuk memeriksa apakah setiap item ada dalam variabel $global-filter
.
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; } }
Maka hanya masalah kembali ke bagian berikut untuk menerapkan mixin filter()
dengan benar:
-
objects/_box.scss
-
objects/_layout.scss
-
utilities/_alignments.scss
Pada titik ini, kembali ke $imports
dan aktifkan hanya komponen expander. Dalam stylesheet yang dikompilasi, selain gaya dari lapisan generik dan elemen, Anda hanya akan melihat yang berikut ini:
- Kelas blok dan elemen milik komponen expander, tetapi bukan pengubahnya.
- Kelas blok dan elemen milik dependensi expander.
- Setiap kelas pengubah milik dependensi expander yang secara eksplisit dideklarasikan dalam variabel
$dependencies
.
Secara teoritis, jika Anda memutuskan ingin memasukkan lebih banyak kelas dalam stylesheet yang dikompilasi, seperti pengubah komponen expander, itu hanya masalah menambahkannya ke variabel $global-filter
pada titik deklarasi, atau menambahkannya di beberapa titik lain di basis kode (Selama itu sebelum titik di mana pengubah itu sendiri dideklarasikan).
Mengaktifkan Semuanya
Jadi kami sekarang memiliki sistem yang cukup lengkap, yang memungkinkan Anda mengimpor objek, komponen, dan utilitas ke kelas individual dalam parsial ini.
Selama pengembangan, untuk alasan apa pun, Anda mungkin hanya ingin mengaktifkan semuanya sekaligus. Untuk memungkinkan ini, kami akan membuat variabel baru bernama $enable-all-classes
, dan kemudian menambahkan beberapa logika tambahan jadi jika ini disetel ke true, semuanya dikompilasi tidak peduli status $imports
dan $global-filter
variabel.
Pertama, deklarasikan variabel dalam file manifes utama kami:
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';
Kemudian kita hanya perlu melakukan beberapa pengeditan kecil pada mixin filter()
dan render()
kita untuk menambahkan beberapa logika override ketika variabel $enable-all-classes
disetel ke true.
Pertama, mixin filter()
. Sebelum pemeriksaan yang ada, kami akan menambahkan pernyataan @if
untuk melihat apakah $enable-all-classes
disetel ke true, dan jika demikian, render @content
, tanpa pertanyaan.
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; } }
Selanjutnya di render()
mixin, kita hanya perlu melakukan pemeriksaan untuk melihat apakah variabel $enable-all-classes
benar, dan jika demikian, lewati pemeriksaan lebih lanjut.
src/scss/tools/_render.scss
$layer: null !default; @mixin render($name) { @if($enable-all-classes or index(map-get($imports, $layer), $name)) { @content; } }
Jadi sekarang, jika Anda ingin mengatur variabel $enable-all-classes
menjadi true dan membangun kembali, setiap kelas opsional akan dikompilasi, menghemat sedikit waktu Anda dalam prosesnya.
Perbandingan
Untuk melihat jenis keuntungan apa yang diberikan teknik ini kepada kita, mari kita jalankan beberapa perbandingan dan lihat apa perbedaan ukuran filenya.
Untuk memastikan perbandingannya adil, kita harus menambahkan objek box dan container di $imports
, dan kemudian menambahkan pengubah o-box--spacing-regular
ke $global-filter
, seperti:
src/scss/settings/_imports.scss
$imports: ( object: ( 'box', 'container' // 'layout' ), component: ( // 'button', 'expander' // 'typography' ), utility: ( // 'alignments', // 'widths' ) ); $global-filter: ( 'o-box--spacing-regular' );
Ini memastikan gaya untuk elemen induk expander sedang dikompilasi seperti jika tidak ada pemfilteran yang terjadi.
Stylesheet Asli vs Difilter
Mari kita bandingkan stylesheet asli dengan semua kelas yang dikompilasi, dengan stylesheet yang difilter di mana hanya CSS yang dibutuhkan oleh komponen expander yang telah dikompilasi.
Standar | ||
---|---|---|
lembar gaya | Ukuran (kb) | Ukuran (gzip) |
Asli | 54.6kb | 6.98kb |
Tersaring | 15.34kb (72% lebih kecil) | 4.91kb (29% lebih kecil) |
- Asli: https://webdevluke.github.io/handlingunusedcss/dist/index2.html
- Difilter: https://webdevluke.github.io/handlingunusedcss/dist/index.html
Anda mungkin berpikir bahwa penghematan persentase gzip berarti ini tidak sepadan dengan usaha, karena tidak ada banyak perbedaan antara stylesheet asli dan yang difilter.
Perlu digarisbawahi bahwa kompresi gzip bekerja lebih baik dengan file yang lebih besar dan lebih berulang. Karena stylesheet yang difilter adalah satu-satunya proof-of-concept, dan hanya berisi CSS untuk komponen expander, tidak banyak yang bisa dikompres seperti dalam proyek kehidupan nyata.
Jika kami meningkatkan setiap stylesheet dengan faktor 10 ke ukuran yang lebih khas dari ukuran bundel CSS situs web, perbedaan dalam ukuran file gzip jauh lebih mengesankan.
10x Ukuran | ||
---|---|---|
lembar gaya | Ukuran (kb) | Ukuran (gzip) |
Asli (10x) | 892.07kb | 75.70kb |
Difilter (10x) | 209.45kb (77% lebih kecil) | 19.47kb (74% lebih kecil) |
Stylesheet yang Difilter vs UNCSS
Berikut perbandingan antara stylesheet yang difilter dan stylesheet yang telah dijalankan melalui alat UNCSS.
Difilter vs UNCSS | ||
---|---|---|
lembar gaya | Ukuran (kb) | Ukuran (gzip) |
Tersaring | 15.34kb | 4.91kb |
UNCSS | 12.89kb (16% lebih kecil) | 4.25kb (13% lebih kecil) |
Alat UNCSS menang sedikit di sini, karena memfilter CSS di direktori generik dan elemen.
Ada kemungkinan bahwa di situs web nyata, dengan lebih banyak variasi elemen HTML yang digunakan, perbedaan antara 2 metode tersebut dapat diabaikan.
Membungkus
Jadi kita telah melihat bagaimana — hanya dengan menggunakan Sass — Anda dapat memperoleh kontrol lebih besar atas kelas CSS apa yang sedang dikompilasi pada build. Ini mengurangi jumlah CSS yang tidak digunakan di lembar gaya akhir dan mempercepat jalur rendering penting.
Di awal artikel, saya mencantumkan beberapa kelemahan dari solusi yang ada seperti UNCSS. Cukup adil untuk mengkritik solusi berorientasi Sass ini dengan cara yang sama, jadi semua fakta ada di atas meja sebelum Anda memutuskan pendekatan mana yang lebih baik untuk Anda:
kelebihan
- Tidak diperlukan dependensi tambahan, jadi Anda tidak perlu bergantung pada kode orang lain.
- Waktu pembuatan yang dibutuhkan lebih sedikit daripada alternatif berbasis Node.js, karena Anda tidak perlu menjalankan browser tanpa kepala untuk mengaudit kode Anda. Ini sangat berguna dengan integrasi berkelanjutan karena kemungkinan kecil Anda melihat antrian build.
- Menghasilkan ukuran file yang serupa jika dibandingkan dengan alat otomatis.
- Di luar kotak, Anda memiliki kontrol penuh atas kode apa yang sedang difilter, terlepas dari bagaimana kelas CSS tersebut digunakan dalam kode Anda. Dengan alternatif berbasis Node.js, Anda sering kali harus mempertahankan daftar putih terpisah sehingga kelas CSS milik HTML yang disuntikkan secara dinamis tidak difilter.
Kontra
- Solusi berorientasi Sass jelas lebih praktis, dalam arti bahwa Anda harus tetap mengikuti variabel
$imports
dan$global-filter
. Di luar penyiapan awal, alternatif Node.js yang telah kita lihat sebagian besar otomatis. - Jika Anda menambahkan kelas CSS ke
$global-filter
dan kemudian menghapusnya dari HTML, Anda harus ingat untuk memperbarui variabel, jika tidak, Anda akan mengompilasi CSS yang tidak Anda perlukan. Dengan proyek besar yang sedang dikerjakan oleh beberapa pengembang pada satu waktu, ini mungkin tidak mudah untuk dikelola kecuali Anda merencanakannya dengan benar. - Saya tidak akan merekomendasikan memasang sistem ini ke basis kode CSS yang ada, karena Anda harus menghabiskan sedikit waktu untuk menyatukan dependensi dan menerapkan mixin
render()
ke BANYAK kelas. Ini adalah sistem yang jauh lebih mudah untuk diterapkan dengan build baru, di mana Anda tidak memiliki kode yang ada untuk dilawan.
Mudah-mudahan Anda menganggap ini menarik untuk dibaca seperti yang menurut saya menarik untuk disatukan. Jika Anda memiliki saran, ide untuk meningkatkan pendekatan ini, atau ingin menunjukkan beberapa kelemahan fatal yang saya lewatkan sepenuhnya, pastikan untuk memposting di komentar di bawah.