Minifikace JavaScriptu

weight loss

Hledáte vhodný zmenšovač JS kódu a napadá vás, že by možná nejjednodušší bylo napsat si něco vlastního, než přizpůsobovat svůj toolchain některému z existujících zmenšovačů? Nebo že se vám nelíbí licence, pod kterou je zmenšovač šířen? Možná vám následující článek ušetří mnoho hodin vlastní práce!

Přiznám se, že cizí knihovny nepoužívám moc ochotně. Nejdřív strávím spoustu času hledáním té, která by mi vyhovovala. Pak často zjistím, že mi stejně něčím nevyhovuje. Když to opravím, pošlu změnu autorovi a on ji přijme, tak čekám na vydání nové verze. Když vyjde, tak musím otestovat, jestli se neporouchalo něco jiného. Cizí knihovny taky často mají závislosti (např. na Javě), které já vyžadovat nechci.

Často si proto radši něco spíchnu sám, protože s tím mám nakonec míň starostí, strávím tím míň času a dělám to, co mě baví mnohem víc, než hledat, zkoušet a domlouvat.

Když jsem hledal minifikátor JavaScriptu pro použití v Admineru, tak jsem se podíval na stávající řešení a vybral JSMin pro PHP. Ten mi v zásadě vyhovoval: neměl žádné závislosti, dělal skoro přesně to, co jsem chtěl, aktualizace probíhaly hladce. Navíc v podobný čas jako Adminer přešel ze SVN na Git, takže i provázání repozitářů bylo bez problémů.

Než…

Než se dobrá duše rozhodla Adminer zpřístupnit jako balík Debianu. JSMin má totiž celkem běžnou licenci, ale s nezvyklým dovětkem: “The Software shall be used for Good, not Evil.” A podle dogmatiků to není dost svobodné, protože chudáci zlořádi teď nemůžou využívat JSMin pro páchání zla. Dokonce by se projekty využívající JSMin neměly hostovat na Google Code.

Když by autor změnil slovo shall na should, tak by se všechno vyžehlilo, ten o tom ale nechce ani slyšet (a já se mu nedivím).

Takže jsem byl slušně požádán, jestli bych nemohl JSMin z Admineru vyhodit. A i když existují jiné kvalitní nástroje, především Google Closure Compiler, tak než to zase řešit, rozhodl jsem se jako obvykle, že si něco spíchnu sám.

JsShrink

Úlohu jsem vyřešil pomocí regulárního výrazu. Ten přeskakuje všechno, co má v JavaScriptu zvláštní význam (řetězce uzavřené do uvozovek nebo do apostrofů a regulární výrazy uzavřené do lomítek). Ze zbytku odstraňuje mezery a komentáře, případně je nahradí novým řádkem, pokud by došlo k nežádoucímu slepení, např. v kódu var a. Mimochodem nový řádek je mnohem důmyslnější než mezera, která se používá obvykle – zabírá shodně jeden bajt a nevytváří kilometr dlouhé řádky, takže se dá kód v nouzi i ladit a nepřekáží při prohledávání.

Nejsložitější jsou JavaScriptové regulární výrazy. Jejich oddělovač koliduje s operátorem dělení a se začátkem komentářů, takže je potřeba sledovat kontext, ve kterém se lomítko vyskytuje. Navíc ani neošetřené lomítko ještě regulární výraz nutně neukončuje: /[/]/ je platný regulární výraz.

Funkce vyžaduje příkazy ukončené středníkem, nespokojí se s jejich ukončením novým řádkem (to mimochodem nesplňují frameworky Dojo a Ext JS). Jinak by se měla vypořádat i s těmi největšími špeky. Např. JSMin nepodporuje platný výraz a++ + +2, zmiňuje to dokonce i v dokumentaci. Dojo ShrinkSafe si zase vyláme zuby na výrazu 8 / /.*/ / 2. Přijde vám výraz nesmyslný? Jistě, ale validní JavaScript to je. Navíc může vracet platný výsledek po následující definici:

RegExp.prototype.toString = function () {
    return 2;
};

ShrinkSafe si ostatně vyláme zuby i na zmíněném /[/]/ a a++ + +2 a chvalozpěv na domácí stránce o tom, jak je tento nástroj bezpečný, protože nepoužívá křehké regulární výrazy, mi přijde poněkud nemístný.

Implementace v JavaScriptu

Regulární výraz je tak jednoduchý (např. nepoužívá lookbehind aserce), že se dá převést i do JavaScriptu, takže si ho můžete vyzkoušet přímo ve svém prohlížeči.

Srovnání

Popis dostupných nástrojů a popis, proč JavaScript zkracovat, vyšel článek na Zdrojáku.

Kód Original JsShrink JSMin ShrinkSafe GCC WS GCC Adv YUI
var a = 1; 10 8 9 10 8 0 8
a++ + +2; 9 9 chyba chyba 9 7 chyba
8 / / .* / / 2; 15 12 chyba chyba 12 12 chyba
/[/]/; 6 6 7 chyba 6 6 6
jQuery 1.7.1 248 235 138 572 139 171 123 951 136 451 84 197 104 684
jQuery 1.7.1 gzip 72 448 39 626 39 755 40 077 39 650 31 579 36 194

Z porovnávaných nástrojů vychází zdaleka nejlépe Google Closure Compiler. Dokáže správně zpracovat všechny vstupy a poskytuje velikostně nejlepší výsledky. Dokonce i ve whitespace only režimu si vede lépe než JsShrink, protože ve skutečnosti neodstraňuje jen bílé znaky, ale i zbytečné středníky, závorky a podobně.

JSMin a ShrinkSafe si se spoustou vstupů neporadily, jiné vstupy byly schopné dokonce i prodloužit (kvůli přidaným prázdným řádkám na začátek nebo konec souboru). Jedině u jQuery si ShrinkSafe vedl slušně, protože zkracuje i názvy proměnných. Po zagzipování se ale tato výhoda úplně ztratí a kód je opět větší než u JsShrink.

YUI Compressor si u jQuery díky agresivnější minimalizaci proměnných vede dobře, zákeřné vstupy ale zpracovat nedokáže.

Závěr

JsShrink se zaměřuje pouze na vypuštění mezer a komentářů, neprovádí zkrácení názvů proměnných, ani jiná kouzla. Dělá to ale svědomitě, snaží se nevyprodukovat jediný zbytečný bajt a zpracovat všechny platné vstupy.

Zdrojový kód: https://github­.com/vrana/JsShrin­k/

Autor se živí programováním v PHP, podílí se na oficiální dokumentaci, vyučuje na MFF UK a vede odborná školení. Poznámky si zapisuje na weblog PHP triky.

Komentáře: 24

Přehled komentářů

Pepe online nástroje
Jakub Vrána Re: online nástroje
blizzz Re: online nástroje
Knyttl Zachování Copyright
Jakub Vrána Re: Zachování Copyright
Knyttl Re: Zachování Copyright
Pepca Re: Zachování Copyright
František Kučera Humor vs. licence
Martin Malý Re: Humor vs. licence
František Kučera Re: Humor vs. licence
fish Re: Humor vs. licence
syntax UglifyJS
Jakub Vrána Re: UglifyJS
kurkuma GCC + DEP
Oldis Re: GCC + DEP
Rekurze Hmmm
Rekurze Re: Hmmm
Michal Google Closure compiler
Jakub Vrána Re: Google Closure compiler
Michal Re: Google Closure compiler
Aichi Re: Google Closure compiler
Michal Re: Google Closure compiler
bckp JavaScript Utility Version 3
Jakub Vrána Re: JavaScript Utility Version 3
Zdroj: http://www.zdrojak.cz/?p=3617