Přejít k navigační liště

Zdroják » JavaScript » Google Closure Compiler: přepište můj kód!

Google Closure Compiler: přepište můj kód!

Články JavaScript, Různé

Zmenšování objemu JavaScriptu je téma, které jsme na Zdrojáku už probírali. Pojďme se tentokrát detailněji podívat na nástroj, který přistupuje k tomuto zmenšování poněkud kreativněji a razantněji než jiné nástroje – Google Closure Compiler totiž váš kód zmenší tím, že ho po vás kompletně přepíše!

Slovo „JavaScript“ má na Zdrojáku, kupodivu, brizanci trinitrotoluenu. Málokterý název programovacího jazyka dokáže vyvolat takové komentátorské vášně a přestřelky jako právě JavaScript, a co do počtu rozpoutaných flamewars se mu může rovnat snad jen věčný spor o umístění otevírací závorky. Což je škoda, protože JavaScript je, i přes to, že je často použit matlaly stylem „namastím tam všechno, co kde najdu“, velmi zajímavý jazyk, který se liší od programátorského mainstreamu a nabízí těm, kdo si s ním dají práci a naučí se ho (doopravdy naučí, ne „naučí“), opravdu pěkné věcí.

Javascript je, s nadsázkou řečeno, „webovým assemblerem“ (viz) – společným jmenovatelem všech prohlížečů, a i přes rozdíly v implementacích je téměř univerzální. Ať se to komu líbí nebo ne, bude tu s námi ještě několik let a bude základem všech webových aplikací. Můžeme o tom vést spory, můžeme s tím nesouhlasit, ale to je tak všechno, co se proti tomu dá dělat.

Uvědomuje si to i webový trendsetter Google a s JavaScriptem rozhodně počítá – z jeho dílen pochází zatím nejrychlejší JS engine V8, od Google vyšel i GWT – nástroj, v němž vytváří své vlastní webové aplikace a který dokáže přeložit zdrojový kód v Javě do JavaScriptu. Google nabídnul i svou sestavu nástrojů Closure, která se skládá z šablonového nástroje, z nástroje na kontrolu správnosti zápisu, z klasické JS knihovny zahrnující UI widgety a DOM funkce, no a konečně z nástroje, o kterém je tento článek:

Google Closure Compiler

O Closure Compileru jsme se na těchto stránkách již zmiňovali v souvislosti s dalšími nástroji na zmenšování kódu. Closure Compiler umí to, co jiné zmenšovače – tedy vynechat nepodstatné mezery a odřádkování (whitespaces) nebo udělat jednoduché zmenšení pomocí přepsání názvů proměnných a parametrů funkcí (simple). To nejzajímavější se ale skrývá pod volbou Advanced.

Při Advanced módu, kterému je věnován zbytek článku, si Closure Compiler „rozebere“ váš JavaScript na prvočinitele, najde nepoužité části, ty vypustí a zbytek přeskládá tak, aby výsledek vyšel co nejmenší. Kromě přejmenování funkcí, proměnných a parametrů dokáže rozhodnout, jestli není některé funkce lepší zapsat inline, a kód přeuspořádá tak, že je sice méně čitelný a srozumitelný, zato kompaktní – tudíž kratší a (pravděpodobně) i rychleji zpracovatelný, protože odpadají některé operace, které mají vysokou režii (což třeba volání funkcí obvykle má).

Samosebou, nic není zadarmo, a takhle agresivní zmenšení s sebou přináší i nároky na zdrojový kód. Ten musí být správný a funkční – pokud bude spatlaný, Compiler jej nezpracuje, nebo jej zpracuje do nefunkčního chuchvalce kódu. Pojďme si ukázat, jaká jsou…

Omezení kladená na zdrojový kód

Můžete si dále zmiňované ukázky procházet v online Closure Compileru – pokud není řečeno jinak, tak na volbu Advanced. Začneme jednoduchým kódem:

function hello(name) {
   alert('Hello, ' + name);
}
hello ('world');

Pokud zkompilujeme tento kód s volbou Simple, dostaneme tento výsledek:

function hello(a){alert("Hello, "+a)}hello("world");

Vstupních 88 bajtů jsme zmenšili na 52. Ovšem oku i jen lehce zkušenějšího programátora neunikne, že v kódu je spousta zbytečností. Například funkce hello se volá jen jednou, tudíž by mohla být zapsána inline. Navíc je volána s konstantním parametrem, který je uvnitř použit zase s konstantou, takže by to – optimalizované – mělo vypadat nějak jako…

alert("Hello, world");

A ano, přesně to je výstupem Closure Compileru, pokud jej přepneme na Advanced.

Každého ale okamžitě napadne: Ale moment, co když je funkce hello() použitá ještě někde jinde? Tak to přestane fungovat! Je to tak. Pokud je funkce hello() použitá ještě „někde jinde“, tak to nebude fungovat. Je důležité si uvědomit, že Closure Compiler bere kód, který mu předložíte, jako monolit, jako jednu jednotku, nad kterou provádí své čachry, a aby to vůbec mohl udělat, tak předpokládá, že žádné „někde jinde“ není. Schválně si zkuste přeložit kód:

function hello(name) {
   alert('Hello, ' + name);
}

tedy bez volání té funkce. Výsledek? Ultimátní! Zmenšení o sto procent, na nula bajtů. (Closure Compiler tak dokazuje platnost starého programátorského vtipu, že „když každý program lze zkrátit o jediný příkaz a zároveň každý program obsahuje alespoň jednu chybu, tak z toho vyplývá, že každý program lze zkrátit na jediný příkaz, a ten bude chybný“ – ve skutečnosti Closure odstraní i ten poslední chybný příkaz a zůstane jen prázdno.)

Export

Pokud máme v kódu funkci, kterou chceme použít někde jinde, tak musíme volání buď přesunout do kódu, nebo to musíme Compileru říct. Používá se k tomu technika nazývaná export. Spočívá v tom, že přiřadíme funkci jako hodnotu nějaké vlastnosti objektu window.

function hello(name) {
   alert('Hello, ' + name);
}
window['hello']=hello;

Výsledkem kompilace bude tentokrát už smysluplnější kód:

window.hello=function(a){alert("Hello, "+a)};

Všimněte si, že v „kódu někde jinde“ můžeme dál volat přímo funkci hello() – vyplývá to z vlastností JavaScriptu.

Konzistence zápisu vlastností

Pozor je potřeba dávat i na jinou věc – Closure Compiler nikdy nezmění literál (zápis čísla nebo řetězce), ale změní si názvy funkcí, parametrů i zápis vlastností (v JS lze odkazovat na vlastnost jak tečkovou notací .jméno, tak i zápisem pomocí ['jméno']). V čem je rozdíl? V tom, že vlastnost zapsanou tečkovou konvencí si Closure Compiler přejmenuje podle potřeby, ale jméno vlastnosti zapsané pomocí literálu ponechá tak, jak bylo zapsáno. Pokud tedy jednou napíšete objekt.title a podruhé objekt['title'], bude pokaždé výsledkem něco jiného: v prvním případě si Compiler vlastnost přejmenuje, ve druhém jméno zachová.

Řešení je jednoduché: Dodržujte jednotnou konvenci. Tam, kde nebudete k vlastnosti přistupovat zvenčí, tedy z kódu někde jinde, používejte tečkovou konvenci a nechte Compiler, aby si vlastnost přejmenoval podle svého. Tam, kde chcete kód navázat na nějaký vnější, a je tedy potřeba zachovat název, používejte zápis pomocí  [].

Důležité to je právě u exportu funkcí (a objektů atd.) Co by se stalo, kdybychom v předchozím příkladu zaměnili window['hello']=hello za window.hello=hello? Výsledný kód by vypadal jinak:

window.a=function(b){alert("Hello, "+b)};

Vidíme, že Compiler si název vlastnosti přejmenoval tak, aby zkrátil výsledný kód. Název nebyl literálem, tudíž jej Compiler nezachoval; důsledkem je to, že volání hello()kódu odjinud selže. Je to sice logické a konzistentní chování, ale občas může vést k podivnému chování výsledného kódu. I proto není, jak jsme si už říkali, Closure Compiler vhodný pro překlad kdejakého bůhvíodkud sebraného kódu, ale vyžaduje disciplinovaného a zkušeného tvůrce skriptů.

Pozor na míchání kódu

Není dobrý nápad přeložit Closure Compilerem jen část kódu, a zbytek ponechat nezpracovaný, obzvlášť pokud se mají navzájem volat. Náklady potřebné na udržení konzistence takového kódu budou vysoké.

Co ale dělat v případě, že potřebujeme z kódu zavolat funkci, která je definována v nějaké knihovně, kterou nemůžeme přeložit s kódem, např. s nějakým JS API? Na to Closure Compiler myslí a nabízí možnost tzv. extern definic, ovšem ty už patří do pokročilých témat – zájemci naleznou víc v dokumentaci.

Anotace

Některé informace o svém kódu můžete sdělit Compileru pomocí anotací – ty využijete i při generování dokumentace. Jejich tvar vychází ze syntaxe nástroje JSDoc a jejich pomocí můžete upozornit překladač např. na text, který má zachovat doslova (licence) či na funkci, která nemá žádné vedlejší efekty a její volání tedy může být vypuštěno, pokud není zpracována vrácená hodnota. Pomocí anotací můžete některé metody označit např. jako soukromé či naznačit rozhraní a dědičnost (extends, implements), čehož Compiler využije a varuje vás, pokud se mu bude zdát, že někde porušujete svoje vlastní pravidla.

Jak Closure Compiler použít?

Máme tři možnosti, jak Google Closure Compiler použít. Můžeme využít už výše zmíněné online služby, což je postup vhodný pro jednorázový překlad. Můžeme využít i toho, že Google nabízí Closure Compiler jako webovou službu s vlastním API. No a konečně můžeme sáhnout k hotové aplikaci v Javě, kterou můžeme použít přímo u sebe, např. na serveru.

Shrňme si závěrem důležité informace o Advanced módu Closure Compileru:

  • Překládejte kód jako jeden celek, jako uzavřenou entitu. Tak dosáhnete nejlepšího kompresního poměru a vyhnete se zbytečným chybám.
  • Closure Compiler je vhodný buď pro celé JS aplikace nebo pro ucelené knihovny, které exportují několik funkcí; asi nebude dávat smysl překládat s ním třeba jednotlivé krátké skripty na obsluhu událostí
  • Closure Compiler je dobrý sluha, ale zlý pán – lajdáckost při psaní kódu se nám vymstí nefunkčním výsledkem, ve kterém budeme dlouho hledat chyby, a nakonec usoudíme, že nám neschopný Compiler akorát rozje*al kód, protože se chová nepředvídatelně. Ve skutečnosti je velmi předvídatelný a chyba bude téměř vždy na straně autora JS kódu – těch výjimek, kdy je třeba přizpůsobit kód Compileru, zase není tolik. Pokud ke kódu přistoupíme s pokorou, dobře si jej zdokumentujeme (anotace pomohou i Compileru) a budeme při psaní pečliví, bude Closure Compiler pracovat k naší naprosté spokojenosti.

Přeji vám hodně pozitivních zkušeností s tímto sympatickým nástrojem.

Komentáře

Subscribe
Upozornit na
guest
22 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
Petr Staníček

Ideální minifier jsem hledal zrovna minulý týden a skončil právě u Closure Compileru. Musím přiznat, že mě především překvapilo, že jako první procesor, z těch, co jsem kdy zkoušel, mi překompiloval poměně velký skript na první dobrou. Po zkušenostech třeba s YUI jsem čekal leccos, jen ne „Compilation was a success“ a výsledný skript stoprocentně funkční.

A ani jsem nečekal, že s tím ten simple mód udělá nějaké velké zázraky, ale dokonce přejmenoval všechno, co šlo, takže výsledek považuju za opravdu slušně upravený: http://colorschemedesigner.com/js/colorschemedesigner.js – a s Advanced mode bych si tady vzhledem k povaze té knihovny moc neškrtl.

blizzboz

moj CMS obsahuje javascriptovú cache ktorá obsahuje 3 druhy kompresie javascriptu JSMIN JSPACK a GZIP.

vačšinou zapnem JSMIN a následne takto skomprimovaný skript ešte skomprimujem GZIPom, následne sa ku klientovi posiela jeden skomprimovaný js súbor, čo dáva najlepšie výsledky. Skúsim pridať aj Closure Compiler neviete či niekde neni dostupný ako Web Service?

blizzboz

dík ešte som článok nečítal, ale určite si ho prečítam…

bauglir

http://code.google.com/closure/compiler/docs/api-ref.html

pro PHP to bude něco takového:

$data = array(
‚compilation_level‘ => ‚WHITESPACE_ONLY‘,
‚output_format‘ => ‚text‘,
‚output_info‘ => ‚compiled_code‘,
);

if ($fileUrl)
$data[‚code_url‘] = $fileUrl; //url scriptu
else
$data[‚js_code‘] = $script; //text scriptu (ale je tam omezení na velikost)

$script = file_get_conten­ts($file);
$ch = curl_init(‚http://­closure-compiler.appspot­.com/compile‘);
curl_setopt($ch, CURLOPT_RETUR­NTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_qu­ery($data));
$output = curl_exec($ch); // výsledek kompilace
curl_close($ch);

ale je to nějaký pátek, co jsem to pouštěl naposledy

bauglir

řádku
$script = file_get_conten ts($file);
si nevšímejte :)

Martin

Pěkný článek, díky. Ale nepoužívejte prosím slovo „Ultimátní“, to je fakt hnus :)

jehovista

„slangové, nepoužívané, nadužívané nebo hovorové“
A do ktere kategorie to slovo spada? Podle me do kategorie „ultimatne blbe“

Daniel Steigerwald

Jen bych dodal, že nejen kompresí živ je Closure Compiler. Komprese je fajn, ale compiler toho umí řádově více. Například kontroluje, jestli potomek abstraktní třídy implementuje všechny metody…, jestli metoda dostává správný typ parametru.. atd.
http://code.google.com/closure/compiler/docs/js-for-compiler.html

Daniel Steigerwald

Jo, ale tahle feature je tak moc killer, že si nezaslouží zapadnou. A tys jí nedal ani jeden malej titulek :-)

anonymous

no ja si furt rikal co stim vsichni maj, ze takovej rozdil oproti yuicomp to neni a ejhle, ono se to musi volat

java -jar compiler.jar –compilation_level ADVANCED_OPTI­MIZATIONS < all.js > min.js

rozdil cca 15% u nekolikatisicoveho prumerneho jquery bastlu. na to ze to nema v sobe kompresor per se (ve vyslednem kodu _neni_ eval()) celkem feat.

_

To nie je celkom ekvivalentný kód.
hello potom bude zmazateľná vlastnosť globálneho objektu, zatiaľ čo predtým bola hello nezmazateľná globálna premenná.
Teda namiesto zrýchlenia a optimalizácie nastane spomalenie, lebo nepôjdu použiť optimalizácie, spoliehajúce sa na to, že je hello nezmazateľná globálna premenná.

_

Že nejde zmazať operátorom delete.
Síce ide zmeniť jej hodnota na niečo úplne iné, teda ide premennú obsahujúcu funkciu prepísať napríklad na textový reťazec, no existovať vždy bude.

Daniel Steigerwald

Někde došlo k šumu, compiler zrovna tohle ekvivalentně kompiluje: http://closure-compiler.appspot.com

Nicméně, pár výjimek skutečně existuje, nicméně jsou popsané a tradeoff.

_

Ale v tom kóde je tá funkcia aj volaná.
Ja som argumentoval tým, že kód s funkciou, definovanou príkazom:
window.hello=fun­ction(a){aler­t("Hello, "+a)};
môže byť interpréterom alebo kompilátorom JS horšie optimalizovateľný, ako s funkciou, definovanou príkazom:
function hello(name){a­lert('Hello, ' + name);}

Milan Kubík

Opravdu můžeme spoléhat na toto řešení? Nezabere nám případné hledání chyby dalších x minut, hodin? Opravdu je rozdíl mezi několika stovkami řádků? Není snad náš vlastní kód lehčeji editovatelný? Co komentáře – kam se počnou? :-) Nečetl jsem diskusi, takže nevím zda-li se to neřešilo, osobně mám radši vlastní kód, vlastní komentáře a vlastní strukturu a návrh aplikace.

_

Predsa ti nič nebráni v tom, aby si používal neverejnú formu skriptu, s komentármi a dlhými názvami premenných, a verejnú – zminimalizovanú formu len ako výstup z tej neverejnej formy.

Milan Kubík

Jde mi především o funkčnost, nikoliv to, zda-li má kód 100 nebo 1000 řádků.

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.