You are viewing an old Wolf's Little Store website. Please visit wolfslittlestore.be for the most recent version.

You are viewing an old Wolf's Little Store website. Please visit wolfslittlestore.be for the most recent version.

CSS onderhoudbaarheid, structuur en performantie

14 januari 2010, 00:26 |

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:

  1. Je CSS is onderhoudbaar
  2. 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:

  1. Je CSS file is best zo klein mogelijkA
  2. Je hebt best zo weinig mogelijk CSS files B

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:

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?

  1. Als de marge verandert moeten we 2 values aanpassen
  2. 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:

  1. Je CSS is onderhoudbaar
  2. Je CSS is duidelijk en goed gedocumenteerd, zodat anderen die hierin werken niet verloren lopen

Echter, deze methode clasht met:

  1. Je CSS file is best zo klein mogelijk (A)
  2. 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>&nbsp;</i>
                    <span>Button text here</span>
                    <b>&nbsp;</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>&nbsp;</i>
                        <span>Button text here</span>
                        <b>&nbsp;</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>&nbsp;</i>
                        <span>Button text here</span>
                        <b>&nbsp;</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>&nbsp;</i>
                        <span>Button text here</span>
                        <b>&nbsp;</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:

  1. 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.
  2. 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:

  1. De relevante versie van je source code checkout vanuit je version control systeem (SVN, Git, Mercurial, Bazaar, of wat je ook gebruikt)
  2. Weet welke files er mogen geüpload worden en welke niet, welke mappen er mogen worden overschreven en welke niet
  3. 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!


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.