CSS onderhoudbaarheid, structuur en performantie
Als je ooit al eens een grote site hebt onderhouden, ken je het volgende probleem wellicht: na een tijdje wordt je CSS file zo groot dat er niet veel structuur meer in zit. Ik onderhoud enkele grote sites waar 5000 lijnen CSS code geen uitzondering zijn. In dit artikel: tips om je CSS te structureren op een onderhoudbare manier, zonder de performantie van je websites uit het oog te verliezen.
Ik hoor de claims al: “5000 lijnen zegt u? Gek! Dat is toch veel te veel? Ik kan dat op minder hoor.” Die line counts zeggen natuurlijk niet echt veel, zeker als je coding style in achting neemt. Het hoe en waarom wordt in dit artikel uitgelegd. Volg even mee.
Sommige mensen schrijven hun code zo (inclusief mezelf):
#globalWarning {
background: #B5121B;
color: #FFF;
position: fixed;
top: 0;
left: 0;
font-weight: 700;
z-index: 3000;
}
Andere zo:
#globalWarning {
background: #B5121B; color: #FFF; position: fixed; top: 0; left: 0; font-weight: 700; z-index: 3000;
}
Of zelfs zo:
#globalWarning { background: #B5121B; color: #FFF; position: fixed; top: 0; left: 0; font-weight: 700; z-index: 3000; }
Als je elke regel op één lijn schrijft kom je uiteraard bij minder lijnen uit. Maar dat doet niet veel goeds aan de leesbaarheid van de code. Ook kan je de CSS file niet meer gemakkelijk naast je HTML file op hetzelfde scherm. Deze coding style heeft natuurlijk 1 groot voordeel: je CSS is kleiner omdat er minder bytes verspild worden aan whitespace characters. Hier komen we later in het artikel nog op terug (minifyen)
Er zijn eigenlijk twee factoren die het verschil maken tussen goede CSS en een rommeltje:
- Je CSS is onderhoudbaar
- Je CSS is duidelijk en goed gedocumenteerd, zodat anderen die hierin werken niet verloren lopen en kunnen werken
Dan is er de performance kant van de zaak:
Die performance kant van de zaak maakt op zich weinig uit als je een klein blogje draait waar je vrienden lezen wat je te schrijven hebt over je kat. Maar hoe groter je site, of hoe meer bezoekers je hebt, begint dit wel van belang te zijn. Waarom moet je hier tijd in steken? De redenen liggen voor de hand:
- Je biedt een betere ervaring aan je bezoekers aan, zeker aan diegene die een tragere connectie hebben en/of op een mobiel toestel surfen
- Je drukt serverkosten: aangezien je files kleiner zijn, verbruik je minder bandbreedte
Naar het schijnt scoor je ook een beetje hoger in Google, maar daar zijn nog geen cijfers over C. Deze performance kant clasht met de zaken die je wil van je CSS: onderhoudbaarheid, structuur. Laat ik beginnen met een voorbeeld. Dit is de layout CSS voor een sidebar:
#sidebar {
width: 200px;
float: left;
margin-left: 20px;
}
De oplettende lezer die veel over browserbugs weet denkt bij het lezen van vorig stukje code “Jaja, double margin bug.”. Inderdaad beste lezer, deze code triggert naar alle waarschijnlijk de IE6 double margin bug D. Om deze op te lossen doen we zo:
#sidebar {
width: 200px;
float: left;
margin-left: 20px;
_margin-left: 10px;
}
Opgelost!
Of toch niet? Wat is er allemaal mis met bovenstaande code?
- Als de marge verandert moeten we 2 values aanpassen
- We gebruiken een fout in de CSS parser van Internet Explorer 6 om een bug op te lossen.
Deze code beter maken in enkele stappen:
#sidebar {
width: 200px;
float: left;
margin-left: 20px;
_display: inline;
}
Nu zijn we al af van (1) 2 values moeten aanpassen als de marge verandert. Nu probleem 2 nog… we maken gebruik van een fout in de CSS parser. Wat is hier fout aan? Stel dat er morgen een browser uitkomt die dezelfde fout in de CSS parser heeft, namelijk regels die beginnen met een underscore toch uitlezen, maar die de double margin bug niet heeft, dan klopt de marge niet meer.
Voor wie er nog is, het wordt interessanter dan dit. Ik wil je intelligentie niet beledigen; natuurlijk weet je wat de double margin bug is. En dat CSS hacks niet kosjer zijn. Dat je je Internet Explorer specifieke code in conditional comments moet zetten.
Dus laten we dat doen:
/* Code contents of file 1: screen.css (every browser) */
#sidebar {
width: 200px;
float: left;
margin-left: 20px;
}
/* File2: ie6.css (IE6 only) */
#sidebar {
display: inline;
}
Proper zo! Maar… met dit te doen, verliezen we een stukje van de onderhoudbaarheid. Neem nu volgende code:
#shazam {
opacity: 0.4;
}
Quizvraag: in welke browsers gaat dit werken? Het antwoord staat in de volgende paragraaf in witte tekst. Selecteren dus om het antwoord te zien.
A: Safari vanaf Safari 3, Firefox vanaf Firefox 2, Opera vanaf Opera 9
Opacity is eigenlijk een zeer goed voorbeeld om mijn punt verder uit te leggen. Er zijn heel veel verschillende manieren om opacity te verkrijgen en elke browser heeft zo wel zijn eigen manieren. De syntax voor opacity verschilt zelfs tussen IE7 en IE8. Meteen ook een reden om deze timesaver E te gebruiken.
Dit moet je schrijven voor cross browser opacity:
#shazam {
opacity: .75; /* Standard: FF gt 1.5, Opera, Safari */
filter: alpha(opacity=75); /* IE lt 8 */
-ms-filter: "alpha(opacity=75)"; /* IE 8 */
-khtml-opacity: .75; /* Safari 1.x */
-moz-opacity: .75; /* FF lt 1.5, Netscape */
}
Ja. Waar zijn die mensen die hun hand opstaken toen ze zeiden dat je IE specifieke CSS in je conditional comments moet steken?
Heb je dan ook specifieke stylesheets voor Firefox 1, Firefox 1.5, Netscape, Opera 7, Opera 8, enzovoort?
Ik heb deze opacity-regel van Snipplr F geplukt. De websites die ik maak bij Netlash en ook binnen mijn eigen bedrijfje, worden getest in alle A-grade browsersG. Laten we deze regel even herschrijven met A-grade support in het achterhoofd:
/* Code contents of file 1: screen.css (every browser) */
#shazam {
opacity: 0.75; /* Standard: FF gt 1.5, Opera, Safari */
}
/* Code contents of file 2: ie6.css (IE6) */
#shazam {
filter: alpha(opacity=75); /* IE lt 8 */
}
/* Code contents of file 3: ie7.css (IE7) */
#shazam {
filter: alpha(opacity=75); /* IE lt 8 */
}
/* Code contents of file 4: ie8.css (IE8) */
#shazam {
-ms-filter: "alpha(opacity=75)"; /* IE 8 */
}
4 declaraties over 4 verschillende files? Dat is niet meteen onderhoudbaar. Dat moet beter.
We kunnen al 1 van de files weghalen door onze timesaver te gebruiken. We verwijderen ie8.css, en we zorgen we dat de in de <head>-sectie van onze website het volgende staat:
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" />
Goed. nog 3 files over:
/* Code contents of file 1: screen.css (every browser) */
#shazam {
opacity: 0.75; /* Standard: FF gt 1.5, Opera, Safari */
}
/* Code contents of file 2: ie6.css (IE6) */
#shazam {
filter: alpha(opacity=75); /* IE lt 8 */
}
/* Code contents of file 3: ie7.css (IE7) */
#shazam {
filter: alpha(opacity=75); /* IE lt 8 */
}
In deze code komt weer hetzelfde probleem als bij de eerste oplossing voor de double margin bug: we moeten 2 waarden aanpassen als er iets wijzigt. Als er beslist wordt de opacity te verhogen of te verlagen, moet iemand er aan denken dat dit deze regel herhaald wordt in ie6.css en ie7.css. Je mag nooit veronderstellen dat jij de enige bent die de code van een project gaat onderhouden (binnen de context van een commercieel project voor een klant in een team).
Als je ooit eens een paar uur bezig geweest met een bug uit andermans code te halen, om dan tot de conclusie te komen dat het geen bug was maar een stukje IE specifieke CSS dat niet geüpdatet is met een verandering, dan lijkt volgende code niet eens zo slecht:
#shazam {
opacity: 0.75;
_filter: alpha(opacity=75); /* IE6 */
:filter: alpha(opacity=75); /* IE7 */
}
De “underscore” hackH heeft ook een variant voor IE7: de “colon hack” I. Om IE6 en IE7 tegelijk te targeten kan je dan ook nog eens dit gebruiken:
#shazam {
opacity: 0.75;
*filter: alpha(opacity=75); /* IE6 and IE7 */
}
Maar zoals eerder gezegd: niet doen! Nog eens: stel dat er morgen een browser uitkomt die dezelfde fout in de CSS parser heeft, gaat die deze regels lezen. En dan ga je onvoorspelbare code krijgen. En als er iets is wat je niet wil in web development, is onvoorspelbare code.
Wat is er wél zeer goed aan bovenstaande notatie? Er is context. Je kan onmogelijk de value van opacity veranderen zonder te merken dat er onder de value ook nog een IE specifieke value staat. Of je bent echt aan het slapen. Hoe combineren we context met voorspelbare code? Ik doe het als volgt:
/* Code contents of file 1: screen.css (every browser) */
#shazam {
opacity: 0.75;
/* @see ie6.css and ie7.css for browser specific CSS for this rule */
}
/* Code contents of file 2: ie6.css (IE6) */
#shazam {
filter: alpha(opacity=75); /* IE lt 8 */
}
/* Code contents of file 2: ie7.css (IE7) */
#shazam {
filter: alpha(opacity=75); /* IE lt 8 */
}
Deze methode zorgt ervoor dat je CSS doet wat de initiële voorwaarden waren:
- Je CSS is onderhoudbaar
- Je CSS is duidelijk en goed gedocumenteerd, zodat anderen die hierin werken niet verloren lopen
Echter, deze methode clasht met:
- Je CSS file is best zo klein mogelijk (A)
- Je hebt best zo weinig mogelijk CSS files (B)
Dit is de commentaar bij de CSS van de verschillende knoppen in tagger.fm:
/*
Winston 0.3 buttons - for irregular background use
--------------------------------------------------
There are a three kinds of main buttons in this project:
To create a button use:
The standard markup is as follows:
***
EXAMPLE <a href="#" class="button">
<i> </i>
<span>Button text here</span>
<b> </b>
</a>
***
Silver gradient background, blue link text = .button
ICONS AVAILABLE:
With play icon on the right hand side
= .button .buttonIconLeft .buttonIconPlay
Silver gradient background, with dropdown icon
= .button .buttonIconRight .buttonIconDropdown
These buttons work on any background.
PNGs are substituted by GIFs in IE6
***
All of the button images are in a single sprite.
The graphics shown are defined via background positioning.
***
To position the buttons, use the button holders
EXAMPLE <div class="buttonHolder">
<a href="#" class="button">
<i> </i>
<span>Button text here</span>
<b> </b>
</a>
</div>
***
Default buttons will float to the left in a button holder.
To make them float to the right, substitute the holder class for buttonHolderRight
EXAMPLE <div class="buttonHolderRight">
<a href="#" class="button">
<i> </i>
<span>Button text here</span>
<b> </b>
</a>
</div>
***
The <b> and <i> elements are used to:
a) provide a hook for icons on the left hand side (b) and the right hand side (i)
b) prevent transparent graphics overlaying each other when using standard sliding doors
c) to easily adjust the side paddings
This example will produce a "play" button:
EXAMPLE <div class="buttonHolderRight buttonIconRight buttonIconPlay">
<a href="#" class="button">
<i> </i>
<span>Button text here</span>
<b> </b>
</a>
</div>
***
*/
Woeps. 1930 bytes ofte 1,8 kilobyte aan comments.
Ik moet nu wel zeggen dat dit mijn meest extreme voorbeeld van commentaar is. Maar dit dient om mijn punt te illustreren; onderhoudbaarheid clasht met performance.
Dat probleem valt ook op te lossen. Maar het is ingewikkelder. Zoals Yahoo dat zegt: je moet je CSS minifyen. Eén manier om dat te doen is met regular expressions. Een andere manier is je CSS door een compressor draaien, zoals de YUI compressor. Uiteindelijk zijn dat ook maar een hoop regular expressions.
Hoe je dit juist doet en hoe die regular expressions werken is buiten de scope van dit artikel.
Je maakt dus 2 versies van je CSS files: een werkversie, en een minified versie. Laad de minified versie in op de live website. Als je verderwerkt aan de website, werk je verder in de werkversie. En als je klaar bent, draai je je scripts, en zet je de files live. Klaar is kees.
Euhm. Niet echt. Als ik serieus aan één van mijn grotere projecten op het werk aan het werken ben dan zijn hier een aantal problemen:
- Er zijn elke dag wel veranderingen aan minstens 1 grote website, dus ik moet elke dag minified versies maken, en dit kost tijd. Het komt voor dat ik 3-4 keer op dezelfde dag een update doorvoer.
- Een groter project met subsites (e.g. Vorst Nationaal en 5 zustersites) bevat… véél CSS files. 1 globale file, dan site specifieke files, dan ie6 en ie7 files voor elke site, print stylesheets, enzovoort. Dit komt neer op een 15-tal files voor 6 sites. Allemaal in 1 groot project. Meer files minify’en kost nog meer tijd.
Gelukkig is hier óók een oplossing voor.
Proficiat aan diegenen die al hier geraakt zijn in het artikel. We zijn er bijna.
Als je het live zetten van je sites automatiseert, kan je zorgen dat dit een stap is in het proces van een site live zetten.
De old school manier om veranderingen live te zetten is naar je FTP client gaan, naar de juiste map gaan, en de bestanden overschrijven.
(Ik veronderstel in alles dat je je website lokaalt ontwikkelt, via een lokale webserver, en een lokale database. En dat je version control gebruikt. Als je beide niet doet ben je slecht bezig. Niet enkel volgens mij, maar ook volgens Joel J.)
De betere manier is zorgen dat je je website live kan zetten met een druk op de knop. In de praktijk betekent dit: een script draaien dat:
- De relevante versie van je source code checkout vanuit je version control systeem (SVN, Git, Mercurial, Bazaar, of wat je ook gebruikt)
- Weet welke files er mogen geüpload worden en welke niet, welke mappen er mogen worden overschreven en welke niet
- Scripts uitvoert om de performantie van je live site te verbeteren: CSS en Javascript minify’en. Apache regels die je performance verbeteren instelt. Caching. Enzovoort.
En dat laatste bestaat nog niet in ons huidig proces. Stap 2 van de Joel test is “Can you make a build in one step?”. Het antwoord is voorlopig nee. Een tool zoals CapistranoK kan helpen met dit proces. En dat is een verbeterpunt dat ik zo snel mogelijk wil doorvoeren voor de grote sites die ik onderhoud.
Voor mijn privéprojecten gebruik ik Compass L en SassM. Compass bevat een compiler met opties. Voor je een site live zet kan je de opties in je compiler veranderen naar “compressed CSS”. Die haalt automatisch de comments en whitespace weg. In een ideale wereld kan ik Compass gebruiken voor al mijn werk.
Maar: hier komt weer regel 2 naar boven: “Je CSS is duidelijk en goed gedocumenteerd, zodat anderen die hierin werken niet verloren lopen en kunnen werken”. Helaas heeft Compass een stijle leercurve. En als er een stagair binnenkomt op Netlash moet die ook in mijn code kunnen werken. Al mijn collega’s moeten mijn code begrijpen en kunnen aanpassen. Ik kan geen 14 mensen verplichten een nieuwe taal te leren omdat dat mijn leven gemakkelijker maakt.
Zoals de mensen van pinboard.in dat zo mooi zeggenN: “A rule of thumb that has worked well for me is that if I’m excited to play around with something, it probably doesn’t belong in production.”
Dat was het voor vandaag. Proficiat aan diegenen die hier zijn geraakt, en deel zeker jullie maintenance ervaringen in de reacties. Over and out!
Voetnota’s
- Yahoo! - Best Practices for Speeding Up Your Web Site - Minify Javascript and CSS
- Yahoo! - Best Practices for Speeding Up Your Web Site - Minimize number of HTTP requests
- Search Engine Land 0 Site Speed, Google’s Next Ranking Factor
- The IE5/6 Doubled Float-Margin Bug
- Timesaver
- Snipplr Opacity snippet
- A-grade browsers Yahoo!
- Underscore hack
- Colon hack
- The Joel Test: 12 Steps to Better Code
- Capistrano
- Compass
- Sass
- Technical Underpinnings
8 reacties op “CSS onderhoudbaarheid, structuur en performantie”
Reacties gesloten
Het is niet meer mogelijk om reacties te geven op dit bericht. Na plaatsing is het gedurende 2 weken mogelijk om te reageren. Dit om de kwaliteit van de uiteindelijke post hoog te houden en spam tegen te gaan. Mocht je toch nog (persoonlijk) willen reageren, zie de contactpagina.
Als je in grotere teams werkt, raad ik aan om “ontwikkel lokaal” niet te interpreteren als “op je eigen pc”, maar als “op een eigen ‘directory’ op een development server”. Als je meerdere developers hebt met elk een eigen omgeving, vergroot je de kans dat er fouten of onverwachte problemen opduiken door een verschillende configuratie van die lokale omgevingen.
Voor je build process kan je idd tools als Capistrano gebruiken, maar heb je daar niet meteen de tijd en/of prerequisites voor, dan kan een simpel bash script je ook al op weg zetten. Version control, YUI Compressor, files bundelen en ftp/rsync, kan perfect gecontroleerd worden vanaf je CLI.
En, las ik erover of ontbreekt het bundelen van files in dit - anders volledig - overzicht? Heb je css files van 5000+ lijnen, dan zijn daar vast een aantal grote brokken in te herkennen; css reset, styling van forms, algemene structuur van site, styling van specifieke “widgets”. Steek al die brokken gewoon in aparte files. In je development versie include je de aparte files, en je build script plakt de juiste files aan mekaar en die file include je in je live environment. (Voorzie wat eenvoudige logica in je platform zodat die weet welke file te includen in welke environment.) Je wil zoveel mogelijk kleine files omdat je dan zelf sneller iets terugvindt, de kans op version control conflicts kleiner is, en ook anderen je css-structuur sneller zullen begrijpen.
Wat Jurriaan zei. :)
En nog dit: dankzij Firebug, de WebKit inspector en de IE Developer Toolbar gebeurt het eigenlijk nog maar weinig dat ik niet precies weet welke css op een bepaald element wordt toegepast, of waar ik een bepaalde css regel kan terugvinden.
Maar voor de rest heel geldige punten wat betreft minifyen, browser hacks vermijden en uitgebried documenteren.
@Jurriaan Ik ga niet echt akkoord met je development server. Bepaalde collega’s van me werken op pc, zelf werk ik op mac. Hier is het wel handig dat we lokaal ontwikkelen (maar wel met een centralised DB), omdat we bepaalde fouten tegenkomen die platformafhankelijk zijn.
Vanuit ons versie systeem gaan we wel naar een interne previewserver gaan publiceren, maar de laatstse stap om geautomatiseerd naar de klant de “gewijzigde” files te gaan overzetten is hier ook nog niet aanwezig.
@wolfr Wat compass betreft kan ik je wel volgen dat het verdomd gemakkelijk is om snel css te schrijven met een public of privé framework. Enige nadeel wat ik vind aan compass is dat bepaalde zaken meerdere keren voorkomen (vb. als je de clearfix mixin gebruikt). Maar das dan eerder een andere discussie over classnames die layout beschrijven (wat clearfixes en grids uiteindelijk wel doen).
Mss toch nog een vlugge vraag hieromtrent: Wat lijkt voor jou de beste manier? Beschrijvende classnames als clearfix en col-x-y gebruiken om zo minder css code te hebben, of niet beschrijvende classnames gebruiken maar dan ofwel meerdere malen dezelfde css blokken te hebben òf uren puzzelen om je selectors zo te groeperen dat de overeenkomende code maar 1 keer geschreven moet worden?
@joggink: Met 20+ developers en gebruik makend van bv. verschillende php extensies, memcache-servers, e.d. lijkt lokaal ontwikkelen me een hel qua configuratie. Ik wil niet de persoon zijn die een update moet gaan doorvoeren, en je kan ook niet van elke developer verwachten dat die zich daarmee bezighoudt.
Als je platformonafhankelijkheid (en dan hebben we ‘t enkel over server-side, toch?) wil testen, zorg je m.i. beter voor meerdere staging-servers op verschillende platformen en een hoop unit tests die die op zoek gaan naar problemen?
Als jij nu ontwikkelt, ontwikkel je zo dat het werkt op mac, als je collega ontwikkelt, ontwikkel je zo dat het werkt op pc. Het ontdekken van fouten gerelateerd aan het platform gebeurt dan doordat je een probleem opmerkt aan de code van je collega tijdens het werken aan je eigen changes. Dat is dan puur toeval?
Jurriaan: Ik zou het heel interessant vinden om wat meer te weten te komen hoe je die ontwikkelomgeving praktisch organiseert.
Kan je hier wat meer informatie over geven? (misschien via een eigen blogpost?)
In een verdere post ga ik hier verder op in :). Dit is inderdaad een van de zaken die niet optimaal zijn in Compass/Sass.
Dit klopt, maar verreist inderdaad build scripts. De prerequisites van een Capistrano (e.g. environment flags) zijn een prerequisite die inderdaad te vermijden valt door een simpele reeks scriptjes.
Hier zijn we aan het werken om dit op een zinnige manier te doen. Veel websites vs. 1 bigasss website (Netlog) is een andere situatie.
:)
Sigh … why do we put up with this?
@Jurriaan: Da’s inderdaad een hel om te onderhouden. Zelf zaten we met het probleem dat er bij wijziging aan één of andere asset alle developers de boel moeten updaten. Dit hebben we nu opgelost door alles op een soort “asset server” te plaatsen. Probleem is dat we zoiezo lokaal code moeten kloppen, testen, en vervolgens committen. Dan pas wordt het gedeployed naar een preview/staging server.
Feit is dat we nog altijd op zoek zijn naar de meest ideale workflow, dus, om Bert te volgen, graag had ik hieromtrent ook wat meer te weten willen komen.