Zmenšujeme JavaScript

Rychlosti internetových přípojek stále rostou a na nějaký ten megabajt dnes už skoro nikdo nehledí (uživatelé mobilního připojení to mohou vidět jinak). Přesto není příliš přívětivé posílat návštěvníkům stránek velké obrázky nebo skripty o velikosti stovek kilobajtů. V článku si ukážeme, jak zmenšit skripty v JavaScriptu.
V programování se, stejně jako v životě, setkáme se spoustou protichůdných rad, jako je třeba známá dvojice přísloví „Mluviti stříbro, mlčeti zlato“ a „líná huba – holý neštěstí!“ Takovou programátorskou obdobu tvoří dvojice požadavků, kdy jeden říká, že by zdrojový kód programu měl být hojně dokumentovaný, aby se v něm vyznali i ostatní, a proti němu jde požadavek nezatěžovat přenosovou linku nefunkčními zbytečnostmi, přičemž právě dokumentační komentáře jsou z hlediska funkce programu zcela zbytné.
Tento paradox, ostatně jako spousta dalších, je jen zdánlivý. Důležité je aplikovat správné pravidlo ve správné situaci. Tedy: Když píšeme program, tak jej hojně komentujeme a odsazujeme a zpřehledňujeme, ale když jej posíláme na server nebo dokonce klientovi, tak z něj všechny „zbytečnosti“ (věci, které stroj stejně ignoruje) předem vypustíme tak, aby program fungoval stále stejně, ale byl menší. (Na webu, plném interpretovaných jazyků, paradoxně „menší“ znamená často i „rychlejší“.)
Zmenšit lze téměř vše, co se na běžné webové stránce vyskytuje – obrázky, skripty, styly i HTML kód. Zmenšené soubory lze navíc před posláním zkomprimovat např. pomocí gzip, pokud prohlížeč tuto techniku podporuje (většina ano). Ale o téhle metodě v článku řeč nebude. Řeč bude o nástrojích, které zmenší skripty pomocí ekvivalentních úprav – tedy takových, kdy je funkce zachována, ale výsledný kód je menší. Omezíme se na nástroje pro JavaScript, zmenšovače HTML a CSS necháme stranou, i když principiálně jsou metody podobné.
Jak zmenšit skript
První krok ke zmenšení jsme si už řekli: Vypusťte všechny komentáře. Ti nejpečlivější programátoři tak mohou ušetřit i přes 80 procent dat. Vypustíme všechny poznámky, všechny informace o verzi, všechny licenční informace (pokud to lze) – nebo myslíte, že do zdrojového kódu vašeho dynamického menu v JavaScriptu kouká někdo, kdo takové informace ocení?
Druhý krok ke zmenšení je rovněž jasný: Vyhoďte zbytečné mezery, odřádkování, tabelátory, zkrátka veškeré „bílé znaky“. Interpret je stejně přeskakuje, tak mu ušetřete práci (a než mě někdo chytí za slovo, tak dodám, že jsou jazyky, kde odsazení má svou syntaktickou roli, proto zdůrazňuji nadbytečné mezery).
Třetí krok: Zkraťte identifikátory. Z hlediska programátorského stylu je samosebou dobré, pokud se funkce jmenuje kontrolaHodnoty()
, ale pokud je volána na patnácti místech (některé formuláře jsou už takové…), tak nám její přejmenování na k()
ušetří skoro čtvrt kilobajtu. Přejmenujte proměnné na jednoznakové – ano, přesně tak, jak se to nemá dělat, když píšete kód.
Dovolte odbočku: V jisté firmě jsem narazil na interní pravidlo, které říkalo, že žádná proměnná se nesmí jmenovat i, j, q, abc0 nebo podobné nicneříkající jméno, a vedoucí programátor na tomto pravidle zcela rigidně lpěl, a ani konvenci „interní proměnné cyklu jsou i, j…“ neuznával, takže konstrukce for (var promennaCyklu=0; promennaCyklu<pocet; promennaCyklu++)
byla zcela běžná. Pravidlo zvítězilo nad zdravým rozumem… Každopádně: Dovedete si představit úsporu přenášených dat zkrácením jmen proměnných v téhle firmě?
Čtvrtý krok ke zmenšení skriptů je: Napište to jinak, a kratší!
Vedlejším efektem většiny zkracovacích postupů je „znečitelnění“ zdrojového kódu. V hodně zmenšeném kódu už nikdo nic ručně neopraví a pravděpodobně z něj ani nevyčte – což sice přináší nutnost udržovat vedle sebe verze „rozbalené“ a „sbalené“, ale na druhou stranu se může „znečitelnění“ někdy hodit.
Problém těchto metod je, že jsou poněkud pracné na ruční provozování pokaždé, když změníme zdrojový kód. Proto vznikly „zkracovače kódu“, které výše uvedené kroky udělají automaticky za vás. Jsou dostupné v nejrůznějších podobách – od programů přes skripty v PHP, Pythonu atd. až po webové služby. Ukážeme si tři nejznámější nástroje pro zmenšení JavaScriptu: JSPacker, YUI Compressor a Closure Compiler, a lehce porovnáme jejich schopnosti.
JSPacker
Autorem JSPackeru je známý javascriptový programátor Dean Edwards. Jeho packer nabízí kromě základního zkrácení (vypuštěním komentářů a mezer) i zkrácení identifikátorů a cosi, co nazývá „Base62 encode“, při kterém je celý zdrojový text rozsekán na řetězce, opět složen a zprovozněn pomocí eval()
– takto upravené zdrojové kódy poznáte podle charakteristického „podpisu“: začínají textem eval(function(p,a,c,k,e,r)
. Pro porovnání jsem vybral zdrojový kód algoritmu SHA-256, který pochází z webu Webtoolkit. Velikost tohoto kódu v nezabalené podobě je 4182 byte. Porovnání jednotlivých metod komprese pak vypadá takto (první sloupec obsahuje název metody, druhý velikost výsledného souboru, třetí kompresní poměr:
Prostá komprese | 3073 | 0.735 |
Shrink variables | 2802 | 0.67 |
Base62 | 3054 | 0.73 |
Base62 + Shrink | 2962 | 0.708 |
Vidíte, že vypuštěním komentářů, vypuštěním mezer a přejmenováním proměnných a funkcí jsme ušetřili třetinu objemu. U takto malého souboru není úspora metodou Base62 vidět, ale např. u velkých knihoven (jQuery) je rozdíl velmi markantní (zmenšení na 0.8 se Shrink vs. zmenšení na 0.55 při použití Base62).
Packer je dostupný i jako aplikace v PHP, Perlu, .NETu a WSH: Packer download.
YUI Compressor
Yahoo nabízí v rámci svého open source projektu YUI (Yahoo User Interface) i kompresor, nazvaný prostě YUI Compressor. Na rozdíl od předchozího nástroje je YUI Compressor aplikace napsaná v Javě a volaná přes příkazový řádek (lze ji tedy použít a zakomponovat do deploy procesu a skripty zmenšovat transparentně až v okamžiku nasazení na server). Nabízí zhruba srovnatelné možnosti jako předchozí packer, výsledky má u velkých knihoven nepatrně horší, u testovací SHA-256 byl o několik bajtů lepši.
Google Closure Compiler
Poměrně novým nástrojem pro zmenšování zdrojových souborů v JavaScriptu je Google Closure Compiler, součást sady Closure Tools. Práce s ním je popsána poměrně podrobně v článku Compressing your JavaScript with Closure Compiler.
Samotný Compiler je open-source nástroj, který si můžete stáhnout. Google jej však nabízí i jako službu, a to jednak přes webové rozhraní, jednak přes RESTful API. Compiler nabízí tři úrovně komprese: První dvě odpovídají předchozím nástrojům (tedy vynechání mezer a komentářů, resp. totéž se zkrácením identifikátorů), třetí (Advanced) realizuje to, co jsme si v úvodu popsali jako bod 4: Přepracujte kód.
Ano, Closure Compiler vaše skripty přepíše: Vynechá nepoužité funkce, některé funkce převede na inline tvar, přejmenuje identifikátory, vyhodnotí zapsané konstantní výrazy, … Přepsáním kódu lze dosáhnout (podle Google) až dvoutřetinové úspory místa. Vedlejším efektem je kontrola správnosti kódu.
S testovacím souborem SHA-256 dosáhl Closure Compiler na úrovni Advanced velikosti 2182 bajtů, což představuje kompresní poměr 0.53.
Při testování pozor! Pokud budete kompilovat pouze soubor s knihovními funkcemi na Advanced, dostanete ultimátní kompresní poměr
0. Vysvětlení je jednoduché: Protože soubor obsahuje pouze funkce, které nejsou nikde volány, usoudí Compiler, že jsou zbytečné, a vyhodí je. Proto je potřeba funkce, které jsou volány odjinud, „exportovat“. Dokumentace doporučuje např. trik s přiřazením do objektu window: window['SHA256'] = SHA256;
čímž se funkce stane „použitou“, a Compiler ji nevyhodí.
Závěrem
Výše popsané metody zmenšování JavaScriptu nejsou rozhodně všechny, ale představují průřez těmi nejznámějšími. Z dalších nástrojů stojí za zmínku dva: Pravděpodobně prvním známějším JS packerem byl JSMIN Douglase Crockforda. Knihovna Dojo používá vlastní minifier s názvem Dojo ShrinkSafe. Srovnání těchto zmenšovačů, JSPackeru a YUI Compressoru nabízí web Javascript Compressor and Comparison Tool.
Samosebou lze výsledky takto zkrácených kódů gzipovat a ušetřit tak další kilobajty přenosového pásma.
Zdánlivě vypadá úspora pár kilobajtů jako nevýznamná; když si ji však vynásobíme počtem návštěvníků webu, objeví se rázem v jiném světle – ušetřených 100 kB se na větším webu celkem rychle promění v gigabajty (zbytečně) přenesených dat… Navíc s rozšiřováním mobilního webu, který je zatím leckdy pomalý a/nebo omezený FUP co do objemu přenesených dat, je úspora přenesených dat rozhodně zajímavá.
Jen bych doplnil pár informací z praxe.
JSPacker už dnes nikdo soudný nepoužívá. Dekomprese totiž probíhá v samotném Javascriptu, a pro takové Mootools, zabere klidně 300ms. Jediný důvod pro použití JSPackeru je situace, kdy není k dispozici GZIP (JSPacker má vysoký kompresní poměr), a v prohlížeči je nám ukradené, jak dlouho se kód rozbaluje. Nedovedu si takovou situaci představit ;)
Oproti tomu YUI Compressor ani Google Closure Compiler, žádnou vlastní kompresi (pomocí eval) neprovádí. Neexistuje tedy žádné zdržení při rozbalování. Dojo Shrinksafe snad ani nemá smysl zmiňovat, protože se dlouhodobě nevyvíjí.
YUI Compressor je nejlepší řešení pro ty, co chtějí kód bezpečně minimalizovat, a nechtějí u toho přemýšlet.
Google Closure Compiler je bomba. Takový kompresor sem si vždy přál =) Od YUI se liší možnostmi svého advanced módu. V článku se píše: „přejmenuje identifikátory“, což pro lokální proměnné YUI činí rovněž. Closure Compiler ale minimalizuje identifikátory všechny! Tedy i enumerace, jmenné prostory, klíče v objektech. Krom toho, že úspora je maximální, dostaneme dárkem i perfektní obfuskaci kódu.
Nástroj pro online porovnání účinnosti komprese.
Díky za doplnění technického úhlu pohledu. Closure Compiler je moc hezký, dokonce jsem zjistil, že i to window[‚SHA256‘]=SHA256 změní na window.SHA256 = … :) A ten nástroj je v článku odkázaný – že tys to nedočetl? ;)
Jsi si jistý, že jsi tam ten link nedodal až po mém komentáři? (řekni prosím že jo) ;)
Musím tě zklamat: Jsem si jistý, že tam je od začátku… :)
Pro doplnění jeden kompresor z české dílny KJScompress
Někdy se může hodit i opačný nástroj – převést „zkrácený“ a „obfuskovaný“ kód na něco stravitelnějšího: http://jsbeautifier.org/ (via http://simonwillison.net/)
Jsme na rootu, takže nepřekvapí že se „vypustil“ MS Ajax Minifier :-) Každopádně výsledky nemá špatné a pokud by to někoho zajímalo trochu do hloubky může kouknout na http://www.coderjournal.com/2010/01/performance-optimizations-made-by-microsoft-google-and-yahoo-javascript-minimizers/
Ne, to není tím, že jsme na Rootu, to je tím, že o MS Ajax Minifieru slyším prvně – ani při shánění podkladů jsem na něj nenarazil (je tedy pravda, že jsem nechtěl jít příliš do hloubky a zmínit každý minifier). Každopádně díky za doplnění.
Jak je to s výkonem. Je minimalizovaný kód i výkonnější nebo je to jedno? Víte někdo?
Compilery vetsinou maji funkci, ze ti odstrani komentare, mezery, nove radky a dalsi znaky ktere neovlivni tvuj kod, kvuli redukci velikosti souboru pro rychlejsi nacitani souboru, tak docela tezko.
Trošku obšírnější odpověď:
U klasických kompilovaných jazyků (C, Pascal atd.) je víceméně jedno, jak dlouhý je zdrojový kód – kompiler ignoruje komentáře, a pokud je dobrý (většina je), tak dokáže do jisté míry napravit i programátorské zhůvěřilosti a „optimalizovat“.
U interpretovaných jazyků, které nepoužívají překlad do bytecode ani cache, se objem může projevit na rychlosti – přeci jen parser musí přeskákat mezery a komentáře při každém načtení.
Jazyky, které jsou na pomezí (tedy překládají do bytecode a ten pak interpretují, nebo implementace, která si cachuje rozparsovaný kód) jsou na tom podobně jako jazyky kompilované – zpomalí to pouze první načtení, po překladu do bytecode už pojede naplno (např. Python a jeho .py a .pyc).
V assembleru se dokonce dělal přesný opak – např. se rozepisovaly některé smyčky na rozbalený tvar a tím se ušetřil čas (jen ten strojový…)
Ale na webu to lehké zvýšení výkonu přinést může – ovšem hlavně v tom, že se skript výrazně rychleji načte (a pokud to, jak výše psal Daniel Steigerwald, není založené na extra pomalé funkci eval()).
Jde o to jakým způsobem minimalizovaný, (z PHP) určitě bude rychlejší zpracování echo ‚Čao světe‘; než $pozdrav = ‚Čao‘; $world=‚světe‘; $text=$pozdrav.‘ ‚.$world; echo $text; – sic nepatrně ale skus si představit že takto máš napsaný každý znak v proměnné a pak do proměnných poskládáš slova z proměnných a pak větu… Tak to napsaný web by byl už výrazně pomalejší(nepatrně ale při kompletních stránkách z proměnných by byl výrazný) a při návštěvnosti 100 000 uživatelů denně by to bylo znatelné! Napříč tomu zkracovat strukturu asi nemá smysl, nemyslím si že by funkce psaná:
if
(podmínka)
{
blok…;
}
elseif
(podmínka)
{
blok…;
}
byla pomaleji zpracována jak u příkazu: if(podmínka){blok;}elseif(podmínka){blok;} (mezera je stejně znak stejný jako konec řádku(v zpracování))
To se bavím ale o scriptu zpracovávaným na straně vytíženého serveru, na straně klienta v případě javascriptu, ač nejsem odborník tak usuzuji že to nebude mít absolutně žádný význam až na scripty o 100000 řádcích+ A když tu napíšeš jakým způsobem(je jich mnoho!) chceš zkrátit kód, tak ti povím zda to má citelný smysl, nebo ne…
obfuskator
Byt muze zmenseni kodu prinest uzitek, v pripadech kdy lze, _vzdy_ pouzivejte komprimovani poskytovane webserverem (pripadne vlastni aplikaci). Samozrejme se spravnymi hlavickami pro rozumne cachovani, at to prohlizec za par hodin/dni nemusi stahovat ze serveru znovu. Rozdil „gzipnuty nezmenseny/originalni kod“ vs „gzipnuty a jeste zmenseny kod“ neni prilis znatelny, gzip je relativne ucinna komprese.
Existuje totiz jedna nevyhoda zmenseneho kodu – pri neodladenem skriptu se tezko trasuje pricina chyby. V pripade GCC je moznost vystopovani bugu jeste o rad ztizena (vznika jiny zdrojak).
Samozrejme lze namitnout, ze zmensujeme jen odladeny produkcni kod, ale kdo je bez chyb, ze.. Takhle to v praxi nemusi prilis fungovat (a nefunguje). U beta verzi (v dnesnim pojeti ala Google) ostrych aplikaci bych proto mozna (kvuli pripadnym chybam) zvolil spise gzip originalniho zdrojaku. Velikost nebude o mnoho vetsi nez komprimovana verze a nebudu ztracet cas s preklapenim cisel radku a nazvu identifikatoru, coz sice jde, ale jedna se o zbytecny „opruz“ (navic, pokud se minifikace vmestna na jeden radek (yui), hlaseni o chybe na radku 1 a sloupci 14562 nikoho prilis nepotesi).
Existuji samozrejme i jine duvody k minifikaci, nekdo muze trpet obsedantni touhou obfuskace a nechce puvodni zdrojak zobrazit vubec – taky duvod, byt spise filozofickeho nez technickeho razeni :).
Souhlas. On si stejně prohlížeč ten skript stáhne jen jednou a pak se serveru jen ptá, jestli se nezměnil – a ten mu většinou odpoví
304 Not Modified
, takže kromě těch HTTP hlaviček se už nic nepřenáší. Z tohoto důvodu má smysl spíš než skripty nějak „komprimovat“, spojit je dohromady*, protože pak se prohlížeč dotazuje jen jednou a ty HTTP hlavičky s 304 tam proběhnou jen jednou a ne třeba pětkrát pro pět skriptů.Co se týče „obfuskace“, jsem proti – IMHO na web patří otevřenost a taková tvůrčí spolupráce – inspirace**. Ale když se někdo úzkostně bojí, že by jeho úžasně kvalitní*** Javascript „ukradl“, tak ať, je to jeho věc.
*) když už člověk chce za každou cenu optimalizovat (často to ale bude předčasná optimalizace).
**) nemyslím tím nějaké sprosté kradení skriptů nebo designů, ale prostě inspiraci, vidím na webu nějaké zajímavé řešení, tak se podívám, jak uvnitř funguje a můžu si napsat podobný javascript… příště se třeba zase někdo inspiruje na mých stránkách. Ostatně různé „layouty“ nebo CSS triky mezi sebou webdesignéři taky sdílí.
***) „Na mém systému jsem pracoval již zhruba 10000 hodin. Při sazbě 1.500 Kč/hodinu, kterou si firemně účtuji…“ :-)
Javascript je interpretovany jazyk, takze skor ako o zmensenie suboro kvoli prenosu zo servera ide o zmensenie suboru z dovodu rychlejsieho spracovavania interpreterom Javascriptu.
Omyl, za a) se to týká jen IE < 9, za b) nedá se to ani změřit
Ja sice z javou pracuju, ale do scriptovani moc nevidim.
Rozhodne chci ale autora pochvalit za ten obrazek :)
Sedi k tematu a pripomina mi pani fotografku :D
Trochu mě vytáčí, jak pořád všichni píší „tabelátor“ místo „tabulátor“. Je to odvozeno od slova tabulka. Když stiskneme tabulátor tak tím vytváříme tabulku. Žádnou tabelku čeština nezná.
Zaujima ma ako webdeveloperi v praxi riesia, ze na developer masine je vhodne mat plnu verziu JS suboru a na ostrom srv sa nasadi komprimovana. Hlavne teda v sucinnosti s verzovacim systemom. Pretoze ak mam na developer aj na ostrom srv tzv. working copy (bavme sa o SVN), tak mi hlava nebere ako tieto dve verzie pomocou verzovacieho systemu spravovat.
Idealne by bole, keby sa na ostrom srv okrem toho spojili vsetky JS/CSS do jedineho suboru pre minimalizaciu HTTP rqs.
Vdaka za akykolvek napad.
Dávat ty komprimované javascripty do SVN (nebo jiného verzovacího systému) je prasárna.
1) na kompresi bych se asi vykašlal, viz můj příspěvek výše. Pokud ale musí být:
2) dá se dělat nějakým skriptem – při nasazování nové verze spustíš skript, který ty JS zkomprimuje a nahraje na správné místo.
3) nebo by to šlo dělat „online“ – např. servlet, který si po nasazení aplikace načte ty JS to paměti, zkomprimuje nebo spojí do jednoho a pak je posílá klientům
Hm, buď viz níže, používat na ostrém serveru jiný zdroj (v závislosti třeba na konfiguraci se použije js_dev nebo js_mini).
Druhé řešení je náročnější, ale o řády lepší – v závislosti na uživatelských právech posílat buď původní nebo on-the-fly minifikovanou verzi, tak lze třeba i na ostrém serveru použít debug verzi pro vybrané uživatele (developery).
To druhé řešení používáme a pohodlí / ušetřený čas se nedá ocenit. Výše popsané je asi 1/10 věcí, mj. se js upravuje v rámci lokalizace, řeší drobné nekompatibility s IE (např. čárky za posledním prvkem pole či hash), dynamický class-loader atd.
Pokud máte deployment řešet jinak než ručním kopírováním souborů přes ftp, tak skriptem všechny JS (případně i CSS) soubory concatnete a proženete KJSCompressem, jak jsem odkazoval výše. Na ostrém serveru musíte mít konfigurací zajištěno, že se budou vydávat tyto soubory a ne nekomprimované, tedy v šablonách bude nutná nějaká podmínka dle konfigurační direktivy.