SASS, LESS, Stylus nebo čisté CSS? (2)
Vyplatí se používat CSS preprocesory? A pokud ano, který zvolit? Pokračuji v rozboru tří nejznámějších konkurentů.
CSS preprocesor je nástroj, který vám ze zdrojového kódu zapsaného ve vlastní syntaxi vygeneruje CSS pro prohlížeč. Mezi nejznámější patří SASS, LESS a Stylus. Mají řešit praktické nedostatky samotného CSS.
V předchozí části jsme si ukázali, jak preprocesory nainstalovat. Dnes se jim podívám na zoubek.
Syntaxe
Ač jednotlivé nástroje používají odlišnou syntax, všechny rozumí klasickému CSS. To je nesmírně důležité! Můžete kopírovat existující CSS fragmenty a budou fungovat. Ačkoliv…
SASS používá dvě různé syntaxe, jedna se nazývá SASS (stejně jako
preprocesor), nepoužívá středníky ani složené závorky {} a
místo toho odsazuje mezerami či tabulátory:
// SASS
#main
color: blue
font-size: 0.3em
Druhou syntaxí je SCSS, která vypadá jako klasické CSS a měla být i plně kompatibilní (budu ji pro SASS používat v dalších příkladech). Stejnou syntax má i LESS:
// SCSS, LESS a CSS
#main {
color: blue;
font-size: 0.3em;
}
Stylus také rozumí CSS syntaxi, nicméně upozorňuje, že nejde o 100% kompatibilitu, především proto, že hodně bazíruje na odsazování. V jeho syntaxi lze vynechat složené závorky, středníky a dokonce i dvojtečky:
// Stylus
#main
color blue
font-size 0.3em
Ale pozor! Důležité je, že tyto znaky můžeme vynechat. Pokud vám zápis připadá příliš hutný, klidně si dvojtečky doplňte. Nechcete odsazovat? Vražte tam složené závorky. Velmi podobně funguje NEON. V tomto se zásadně liší od úsporné SASS syntaxe, kde vynechání závorek a středníků je povinnost. Což ji činí z praktického hlediska nepoužitelnou a chápu, proč tvůrci o kompatibilitu s CSS usilovali, ale udělali to nešťastně zavedením další, nekompatibilní, ukecané syntaxe SCSS.
Když už se totiž rozhodnu preprocesor používat, ocením, když mi zjednoduší i syntaxi (alespoň nepovinné středníky), což bohužel umí jen Stylus. Jeho syntax je nejpružnější a nejúspornější.
V kontrastu přísnosti LESS a SASS je zajímavé, že všechny tři
preprocesory podporují // řádkové komentáře.
Když udělám chybu
Pokud uděláte v dokumentu chibu, preprocesory vás na ni upozorní
chybovou hláškou. A v tomto směru se jednotlivé nástroje zásadně
liší. Pokud třeba zapomenu ve výše uvedeném kódu uzavírací
}, oznámí SASS:
Syntax error: Invalid CSS after "font-size: 0.3em;": expected "}", was ""
on line 4 of test.scss
Stylus:
if (err) throw err;
^
ParseError: stdin:4
1| #main {
2| color: blue;
3| font-size: 0.3em;
> 4|
unexpected "eos"
a LESS:
ParseError: missing closing `}` in test.less:9:17 8 9 unexpected "eos"undefined
Nutno dodat, že LESS měl šťastnější chvilku. Pokud bych zapomněl středník, skončí to u obvyklejšího
ParseError: Syntax Error on line 2 in test.less:2:1
1 #main {
2 color: blue
3 font-size: 0.3em
Výmluvné chybové hlášky jsou pro mě důležité a v tomto směru
s přehledem vede SASS. Ačkoliv se to z ukázky tak nejeví, hodně se
snaží i Stylus. Běžně však špatně spočítá číslo řádku a hlásí
chybu o pár řádků výš, než skutečně je. LESS se nejčastěji zmůže
na nicneříkající Syntax Error on line XY.
Občas jsem narážel na chyby v samotném preprocesoru. Pokud se mi je podařilo izolovat, snažil jsem se je nahlásit (LESS, Stylus), ale ne vždy to šlo. Zde se nejvíc projevila vyspělost jednotlivých nástrojů. Zatímco nejstarší SASS se mi z rovnováhy nepodařilo vyvést nikdy, druhé dva nástroje obsahují chyb bohužel požehnaně.
Co všechno umí?
Drtivou většinou užitečných vychytávek najdeme u všech tří nástrojů, byť třeba nemusí být uvedeny v dokumentaci. V množství asi vede SASS, ale takto se to hodnotit nedá. Třeba nested properties nepotřebuji, naopak property lookup a generátor data URI ve Stylusu vypadá užitečně. Tedy do chvíle, než zjistíte, že převádí buď všechny obrázky, nebo žádný.
Nechci sepisovat výčet jednotlivých vlastností ani srovnávat jejich zápis, to najdete v dokumentaci nebo ve srovnávacím článku na NetTuts. Zaměřím se místo toho na fundamentální rozdíly a největší z nich se týkají tzv. mixinů.
Mixiny
Mixiny jsou z mého pohledu nejdůležitější vychytávkou preprocesorů, protože mění způsob, jak píšeme HTML kód. Vysvětlím. Asi nejznámější „mixin“ je tzv. clearfix, která se stará o to, aby z bloku nevytékaly plovoucí prvky. Obvykle vypadá nějak takto:
.clearfix:after {
clear: both;
content: ".";
display: block;
height: 0;
visibility: hidden;
font-size: 0;
}
(Poznámka: existují jednodušší i složitější varianty clearfixu.
Kupodivu mně vždycky fungovalo nastavit bloku overflow: auto a
clearfix jsem nepotřeboval. Ale v tuto chvíli jde jen
o příklad.).
Clearfix pak aktivujeme v HTML:
<div id="content" class="clearfix">
<div style="float: left;">....</div>
...
</div>
Ale to neděláš dobře, Jaromíre. Clearfix by se měl aplikovat na straně
stylů, přímo pro #content, nikoliv v HTML. Jenže komu by se
chtělo kazit si krásný stylopis kopírováním ošklivého hacku? Mnoho
kodérů raději sáhne do HTML.
S preprocesory však netřeba nic kopírovat a vystačíme si s jedinou
instrukcí. Nejprve si clearfix uložíme jako tzv. mixin a poté ho v definici
#content zavoláme:
SASS:
// definice mixinu (zkráceno)
@mixin clearfix {
&:after { clear: both; ... }
}
#content {
@include clearfix; // vložení mixinu
}
LESS:
.clearfix() {
&:after { clear: both; ... }
}
#content {
.clearfix;
}
Stylus:
clearfix()
&:after { clear: both; ... }
#content
clearfix()
Zmizí tak nutkání zasahovat do zdrojového kódu. A to je velmi důležité.
Jak vidno, syntaxe SASS je opět nejukecanější. Za zmínku stojí, že LESS umožňuje volat jako mixin i jakoukoliv třídu nebo ID.
Parametrické mixiny
Ještě zajímavější je to ve chvíli, kdy mixinům předáme parametry:
SASS:
@mixin border-radius($arg) {
-webkit-border-radius: $arg;
-moz-border-radius: $arg;
border-radius: $arg;
}
#content {
@include border-radius(2px);
}
LESS:
.border-radius(@arg) {
-webkit-border-radius: @arg;
-moz-border-radius: @arg;
border-radius: @arg;
}
#content {
.border-radius(2px);
}
Stylus:
border-radius(arg)
-webkit-border-radius: arg
-moz-border-radius: arg
border-radius: arg
#content
border-radius(2px)
Ve Stylusu můžeme použít i zapis s dvojtečkou:
#content
border-radius: 2px
Což je velmi zajímavé! Když totiž později vendor prefix odstraníme, nebo když naopak zjistíme, že určité vlastnosti potřebujeme vendorovou variantu dodat, nemusíme změnit ani čárku v samotném stylu. Wow.
Jenže… co když mixin zavoláme s více parametry? Například uvedeme
border-radius: 2px 3px. Překvapivě Stylus bude 3px
v tichosti ignorovat. Proč tomu tak je?
V CSS existují dva způsoby, jak uvést více argumentů, lišící se v oddělovači:
- oddělené mezerama:
border: 1px solid black - oddělené čárkama:
rgb(10, 20, 30)
Totéž se rozlišuje i v preprocesorech. Ve Stylusu platí, že volání
mixinu border-radius: 2px 3px je ekvivalentní k
border-radius(2px, 3px) (a dává mi to smysl), obojí představuje
předávní dvou argumentů. Naopak border-radius(2px 3px) bez
čárky předává argument jeden. Náš mixin očekává pouze jeden parametr a
druhý tiše bez jakéhokoliv varování zahodil. Oprava spočívá v použití
užitečného klíčového slova arguments namísto předaného
argumentu arg.
SASS a LESS naopak předání více argumentů, než je mixinem očekáváno,
vnímají jako fatální chybu. V případě SASS to v důsledku vede
k podivným blokům $shadow-1, …, $shadow-10 patrným
v galerii Compass, zatímco LESS se s problémem snaží vypořádat
konstrukcí when(). Nic z toho mi nepřipadá ideální.
Pokračování zase příště.
SASS, LESS, Stylus nebo čisté CSS? (1)
Už pár let jsem si pořádně nezakódoval a začalo mi to chybět. Zachtělo se mi udělat stránky podle nejnovějších trendů. Responsivní design okořeněný CSS preprocesory. Ale váhal jsem: jsou preprocesory víc, než jen chvilková móda?
CSS preprocesor je nástroj, který vám ze zdrojového kódu zapsaného ve vlastní syntaxi vygeneruje CSS pro prohlížeč. Mezi nejznámější patří SASS, LESS a Stylus.
Faktem je, že jakmile začne stylopis kynout do větších rozměrů, řada věcí se řeší dosti nepohodlně. Je třeba vynaložit úsilí, aby zůstal čitelný a srozumitelný. Aby se z něj nestal write-only soubor plný magický konstant a hacků. Spoustu těchto nešvarů preprocesory řeší, nejvíc se těším na vnořené definice, matematické výrazy, mixiny a proměnné.
Vlastně je smutné, že preprocesory musely vzniknout. Ukazuje to na zanedbanost vývoje CSS. Na druhou stranu, může z nich i těžit. Preprocesory jsou mladé projekty procházející bouřlivým vývojem, reagují na potřeby uživatelů a lze u nich, na rozdíl od standardu, tolerovat i případné nekompatibilní změny. Ve finále tak mohou ukázat směr, kterým se má vydat příští CSS.
Pokud se kódováním webů bavíte řadu let, máte vybudované konvence pomáhající nedostatky obcházet. Preprocesory pak nemusí být úplně samozřejmou volbou. Nicméně dnes je běžné používat různé CSS generátory a preprocesor nabízí čistější cestu, než copy&pastovat vygenerovaný kód.
Rád zkouším nové věci, proto jsem dal preprocesorům šanci. Který ale zvolit? Nejlepší je si všechny osahat.
Instalace
Začneme tedy rovnou instalací. Na webu SASS i LESS rychle najdete vyčerpávající postup, jak knihovny získat. SASS je napsaný v Ruby, LESS v Node.js, takže prvním krokem bude instalace překladače, což by neměl být v žádném operačním systému problém. Preprocesor pak nainstalujete příkazem:
gem install sass
resp.
npm install less
Velmi snadné, PHP může závidět.
Naopak web Stylusu selhává, snaží se mást odkazy na Github, zatímco
informace, jak ho instalovat, je důmyslně skrytá kdesi vespod úvodní
stránky. Vězte tedy, že Stylus je také napsán v Node.js a nainstaluje se
obdobně příkazem npm install stylus.
Pozor na jednu věc, npm instaluje balíček do aktuálního adresáře, takže
abyste mohli preprocesory pohodlně spouštět z příkazové řádky, musíte
si cestu k lessc.cmd a stylus.cmd přidat do
proměnné PATH. Ve Windows se to dělá sparťansky, takže
spíš oceníte možnost nainstalovat balíčky do globálního adresáře
(pomocí přepínače -g, tj. npm install -g stylus),
ke kterému už cestu zaregistroval při instalaci překladač.
Příkazová řádka pro SASS a Stylus nabízí spoustu voleb, LESS umí jen
konvertovat vstupní soubor do výstupního CSS. Zmátlo mě, že SASS i Stylus
zavolané bez parametrů se nijak neohlásí a očekávají vstup
z klávesnice. Zavolejte je tedy s parametrem -h a vypíše se
nápověda všech voleb.
Vývoj a deployment
Preprocesory vyžadují, aby se mezi úpravou stylopisu a zobrazením v prohlížeči udělat jeden krok navíc: kompilace do CSS.
Tento krůček může mnohé odradit. Pokud jste zvyklí rovnou editovat CSS soubory na FTP serveru, nad preprocesorem vůbec neuvažujte. Existují sice možnosti, jak třeba z LESS generovat CSS za běhu přímo v prohlížeči, ale rozhodně nejsou určeny pro produkční nasazení.
Pokud máte deployment automatizovaný, stačí do procesu zařadit kompilaci voláním příkazové řádky a je vystaráno.
Jak řešit onen krok navíc během vývoje? Kodéra rozhodně blbnutí s příkazovou řádkou nezajímá a chce rovnou psát stylopis.
Jak jsem zmínil, LESS umí překládat stylopisy v prohlížeči, stačí
tedy zalinkovat soubor less.js a můžete rovnou připojovat soubory ve formátu
LESS (povšimněte hodnoty atributu rel):
<link rel="stylesheet/less" type="text/css" href="styles.less">
<script src="less.js" type="text/javascript"></script>
SASS a Stylus zase nabízejí sledovací režim, ve kterém monitorují adresář se styly ve svém formátu a při každé změně souboru je ihned překládají do CSS.
// překlad souborů z adresáře /css.sass do /css (včetně podadresářů) sass --watch css.sass:css // překlad souborů z adresáře /css.stylus do /css stylus --watch css.stylus --out css
Do vygenerovaného CSS lze pomocí přepínače --line-numbers
přidat pro lepší orientaci komentáře s číslem řádku zdrojového
souboru. Pokud vyvíjíte ve Firefoxu, ještě užitečnější je nainstalovat
FireStylus
a kompilovat s přepínačem --firebug. V záložce HTML by se
pak měly objevovat odkazy přímo na zdrojový soubor. Píšu měly, protože
mi to nefunguje.
Všechny tři preprocesory jsou seřazeny na startovní čáře. Který z nich běží nejlépe? Pokračování příště.
Bug in IE9: twice sent cookie is removed
I found a strange bug in Internet Explorer 9, which appears in the last version 9.0.8112.16421. If the same cookie is sent twice, once with the expiration at the end of the session and once with any other expiration, IE9 will remove cookie. Webpage must have a strict doctype.
Example:
<?php
// expire at the end of the session
setcookie('test', 'value', 0);
// expire in 1 hour
setcookie('test', 'value', 3600 + time());
// switch browser and document mode to IE9
?>
<!doctype html>
Is cookie set? <?php echo isset($_COOKIE['test']) ? 'yes' : 'no' ?>
Try to open this example in IE9 and refresh: cookie will not be set.
Solution is to remove older cookies after sending new one:
$headers = array();
foreach (headers_list() as $header) {
if (preg_match('#^Set-Cookie: .+?=#', $header, $m)) {
$headers[$m[0]] = $header;
header_remove('Set-Cookie'); // requires PHP 5.3 or newer
}
}
foreach ($headers as $header) {
header($header, FALSE);
}
Bug is fixed in Nette Framework.
Jsou tyto URL stejné?
Otázka, kterou si klade řada webmasterů: vnímají vyhledávače tyto URL jako stejné? Jak s nimi naložit?
http://example.com/articlehttp://example.com/article/http://example.com/Articlehttps://example.com/articlehttp://www.example.com/article
http://example.com/article?a=1&b=2http://example.com/article?b=2&a=1
Stručná odpověď by byla: „URL jsou odlišné.“ Chce to ale podrobnější rozbor.
Z pohledu uživatelů se tyto adresy liší v drobnostech, kterým nepřikládají žádnou váhu. Tedy je vnímají jako stejné, ačkoliv z technického hlediska jde o adresy různé. Říkejme jim třeba adresy podobné. V zájmu uživatelského prožitku proto dodržujte 2 zásady:
- Nedovolte, aby se na podobných adresách nacházel odlišný obsah. Jak záhy ukážu, nevedlo by to jen ke zmatení uživatelů, ale i vyhledávačů.
- Umožněte uživatelům přístup i přes podobné adresy.
Pokud se adresy liší v protokolu http / https
nebo doméně s www či bez, považují je vyhledávače za
různé. Nikoliv tak uživatelé. Bylo by tedy fatální chybou na takto
podobné adresy umístit různý obsah. Nicméně chybou by bylo
i znemožnění přístupu přes podobnou adresu. Musí tedy fungovat
adresa s www i bez www. Přičemž SEO nabádá
držet se jedné varianty a ostatní na ni přesměrovávat HTTP kódem 301. To
lze u www subdomény zajistit například souborem
.htaccess:
# presmerovani na variantu bez www
RewriteCond %{HTTP_HOST} ^www\.
RewriteRule ^.*$ http://example.com/$0 [R=301,NE,L]
# presmerovani na variantu s www
RewriteCond %{HTTP_HOST} !^www\.
RewriteRule ^.*$ http://www.example.com/$0 [R=301,NE,L]
Schválně si hned vyzkoušejte, jestli vaše servery přesměrovávají a to
včetně celé adresy a správného
předání parametrů. Nezapomeňte i na varianty
www.subdomena.example.cz. Protože některé prohlížeče umí
chybějící přesměrování obcházet, zkuste raději nízkoúrovňovou
službu jako Web-Sniffer.
Velká a malá písmena se v URL rozlišují všude kromě schéma a domény. Avšak uživatel je opět nerozlišuje a je tedy nešťastné nabízet rozdílný obsah na adresách lišících se jen velikostí písmen. Jako špatný příklad může posloužit Wikipedie:
- http://en.wikipedia.org/wiki/Acid o kyselinách
- http://en.wikipedia.org/wiki/ACID o databázových transakcích
Kuriózní chybou trpí Bing, který vrací stejné URL, ať už hledáte kyselinu nebo databázi (ačkoliv textový popis je správný). Google nebo Yahoo tímto problémem netrpí.
Bing nerozlišuje mezi kyselinou a databází
Také některé služby (webmaily, ICQ) převádí velká písmenka v URL na malá, což jsou všechno důvody, proč se rozlišování velikosti vyhnout a to nejlépe i v parametrech. Raději dodržujte konvenci, že všechna písmenka v URL budou malá.
Rozlišit některé podobné adresy je oříšek i pro vyhledávače. Udělal jsem experiment a na URL lišící se v detailech jako je přítomnost pravostranného lomítka nebo pořadí parametrů umístil odlišný obsah. Zaindexovat je byl schopen pouze Google. Ostatní vyhledávače dokázaly pojmout vždy jen jednu z variant.
Jen Google tyto stránky umí indexovat jako rozdílné
Pokud jde o pravostranná lomítka, webový server přesměrovává na kanonickou podobu obvykle za vás; přistoupíte-li k adresáři bez pravostranného lomítka, doplní je a přesměruje. Samozřejmě to neplatí, když URI spravujete ve vlastní režii (Cool URI apod.)
A nakonec: skutečně záleží na pořadí parametrů? Mezi adresou
article?a=1&b=2 a article?b=2&a=1 by neměl
být žádný rozdíl. Jsou ale situace, kdy to neplatí, zejména když
předáváme složitější struktury jako třeba pole. Například
?sort[]=name&sort[]=city může být něco jiného, než
?sort[]=city&sort[]=name. Nicméně přesměrovávat, nejsou-li
parametry ve stanoveném pořadí, bych považoval za zbytečnou
hyperkorektnost.
p. s. Nette Framework přesměrování na kanonické URL provádí zcela automaticky ve své režii
Formuláře a HTML5 - co mi ještě chybí
Pracovat s webovým formulářem na straně JavaScriptu se poměrně snadno může stát očistcem. Nebo jak se nazývá ta věc na čištění záchodové mísy. Přitom za všechno může jedno nešťastné rozhodnutí.
Mějme jednoduchý formulář
<form id=myform>
<input type=text name=query>
<input type=submit value="Vyhledat">
</form>
K jeho jednotlivým prvkům přistoupíme přes vlastnost
elements:
var form = document.getElementById('myform');
var query = form.elements.query.value;
// pochopitelně také form.elements['query'].value
A iterovat nad prvky lze cyklem for:
for (var i = 0; i < form.elements.length; i++) {
alert(form.elements[i].value);
}
Z historických důvodů lze vlastnost elements v příkladech
vynechat, neboť jednotlivé prvky se mapují přímo do objektu
form. Takže by fungovalo i form.query.value,
form.length nebo form[i].value. Což se záhy ukáže
jako nemilé. Formulářové prvky totiž přepisují nativní
metody a proměnné objektu form. Například metoda submit(),
která je klíčová pro AJAXové odeslání formuláře, se stane nedostupnou,
pokud formulář obsahuje prvek nazvaný submit. A uznejte, že to
je zrovinka název pro odesílací tlačítko jako dělaný. Pokud by tedy
formulář vypadal takto
<form id=myform>
<input type=text name=query>
<input type=submit name=submit value="Vyhledat">
</form>
nebude jej možné příkazem form.submit() odeslat, místo toho
JavaScript zařve „form.submit is not a function“ a má pravdu,
form.submit není funkce ale objekt HtmlInputElement. Dobrá, dáme
si pozor a nebudeme formulářové prvky nazývat submit.
(prozradím trik, jak by formulář šlo odeslat i v tomto případě:
document.createElement('form').submit.call(form))
Jenže název submit není tím jediným, kterému se musíme
vyhnout. Prvek pojmenovaný elements způsobí, že
form.elements nebude očekávaná kolekce a výše uvedené
příklady skončí chybou. Název length zase znemožní nad
kolekcí iterovat. A tak by se dalo pokračovat, nativních prvků třeba DOM
Firefoxu definuje hodně přes stovku. Pro zajímavost uvádím seznam jen těch
jednoslovných (tj. vynechávám nodeName nebo
innerHTML):
action, attributes, blur, children, dir, draggable, elements, encoding, enctype, focus, id, lang, length, method, name, normalize, prefix, reset, spellcheck, style, submit, target, title
Bylo by skvělé, kdyby se HTML5 dokázalo s tímto nešvarem vypořádat. Zatím jsem ve specifikaci nic takového nenašel.
Podobně mi chybí možnost, jak v obsluze události onsubmit
zjistit, kterým tlačítkem byl formulář odeslán. Triviální a užitečná
věc a jak složitě se musí řešit.
<form id=myform onsubmit="kterým tlačítkem byl odeslán?">
<input type=hidden name=id value="123">
<input type=submit name=save value="Uložit">
<input type=submit name=delete value="Smazat">
</form>
Řeším to tak, že odchytávám událost click jednotlivých
tlačítek a název prvku ukládám do vlastní proměnné formuláře. O něco
jednodušší je využít bublání a odchytávat click přímo na
formuláři. Nicméně v HTML5 tohle už nebude fungovat spolehlivě, protože
prvek může být umístěn i mimo strom formuláře a přiřazen k němu
atributem form. Bylo by tedy fajn, kdyby HTML5 zavedlo vlastnost
například form.submitter, který by vracela název
odesílajícího tlačítka.
p. s. Nette Framework s těmito situacemi počítá a snaží se je v rámci možností řešit za programátora

