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

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.

Začal programovat v roce 1984 s programovatelnou kalkulačkou. Pokračoval k BASICu, assembleru Z80, Forthu, Pascalu, Céčku, dalším assemblerům, před časem v PHP a teď by rád neprogramoval a radši se věnoval starým počítačům.

Věděli jste, že nám můžete zasílat zprávičky? (Jen pro přihlášené.)

Komentáře: 22

Přehled komentářů

Petr Staníček Dík za článek
blizzboz Re: Dík za článek
Martin Malý Re: Dík za článek
blizzboz Re: Dík za článek
bauglir Re: Dík za článek
bauglir Re: Dík za článek
Martin Pěkný článek, ale ...
Martin Malý Re: Pěkný článek, ale ...
jehovista Re: Pěkný článek, ale ...
Daniel Steigerwald Nejen komprese
Martin Malý Re: Nejen komprese
Daniel Steigerwald Re: Nejen komprese
anonymous Re: Google Closure Compiler: přepište můj kód!
_ Výsledkem kompilace bude tentokrát už smysluplnější kód: window.hello=function(a){alert("Hello, "+a)};
Martin Malý Re: Výsledkem kompilace bude tentokrát už smysluplnější kód: window.hello=function(a){alert("Hello, "+a)};
_ Re: Výsledkem kompilace bude tentokrát už smysluplnější kód: window.hello=function(a){alert("Hello, "+a)};
Daniel Steigerwald Re: Výsledkem kompilace bude tentokrát už smysluplnější kód: window.hello=function(a){alert("Hello, "+a)};
_ Re: Výsledkem kompilace bude tentokrát už smysluplnější kód: window.hello=function(a){alert("Hello, "+a)};
Milan Kubík Opravdu na toto řešení můžeme spoléhat?
_ Re: Opravdu na toto řešení můžeme spoléhat?
Milan Kubík Re: Opravdu na toto řešení můžeme spoléhat?
Martin Malý Re: Opravdu na toto řešení můžeme spoléhat?
Zdroj: https://www.zdrojak.cz/?p=3359