Používame Gulp.js (3) - Optimalizácia webu

Publikoval Michal Kočí dňa 9.8.2015 o 11:00 v kategóriach Gulp.js a Optimalizácia webu

V prvej časti miniseriálu sme si pripravili jednoduchú stránku, v druhej sme si nainštalovali Gulp.js a naprogramovali prvý task. Dnes si doprogramujeme ďalšie tasky, ktorými budeme optimalizovať náš web. Budeme minifikovať, kombinovať a verzionovať štýly a skripty.

Prečo optimalizovať web

Aby ste zrýchlili jeho načítanie v prehliadač a tak vašim návštevníkom spríjemnili návštevu.

Pokiaľ je váš návštevník na rýchlom pripojení, je možné, že vašu optimalizáciu ani nepostrehne, najmä pri malom webe. Ale potom sa k vám vráti cez mobil, pripojený cez 3G a bude niekoľko sekúnd tupo zírať na prázdnu stránku. A to predsa nechcete.

Chceli by ste programovať optimalizované weby? Dojdite na jedno z mojich školení JavaScriptu (Node.js, jQuery, AngularJS, ...), na nich sa okrem iného naučíte, ako naprogramovaný web optimalizovať tak, aby z neho vaši používatelia mali radosť.

Treba si uvedomiť, že spomalenie vzniká v dôsledku:

  • Veľkého množstva dát, ktoré sa musí preniesť zo servera k návštevníkovi (čím väčšie máte skripty, štýly a HTML a čím viac obrázkov, tým viac dát sa musí preniesť)
  • Veľkého množstva dodatočných dotazov. Prvé sa k vášmu návštevníkovi prenesie samotný HTML dokument, ten obsahuje odkazy na štýly, skripty a obrázky. Čím viac odkazov, tým viac spojení so serverom sa musí nadviazať a každé ďalšie spojenie so sebou nesie spomalenie či už kvôli nárokom na vytvorenie samotného spojenie alebo kvôli HTTP hlavičkám, ktoré sa prenášajú na server a spät.

Ako optimalizovať web

Aké sú najčastejšie problémy webu z pohľadu rýchlosti sme si vysvetlili v predchádzajúcom odstavci. Ak navyše váš web neindikuje (cez HTTP hlavičky), že sa tieto súbory môžu v prehliadači kešovať, bude si ich prehliadač sťahovať pri každej návšteve stránky. Pri každej.

Čo s tým môžete robiť?

Server

Na serveri optimalizujete najčastejšie takto:

  • Nastavte správne kešovanie statických súborov (css, js, obrázky), pokiaľ sa vám nemenia, nastavte ich platnosť kľudne aj na 1 rok
  • Povoľte kompresiu (gzip alebo deflate), aby tieto zdroje išli komprimované, dokážete tak znížiť množstvo prenesených dát

Vaša aplikácia

Ak máte serverové nastavenia poriešené (alebo viete, že budete mať), optimalizovať môžete (mali by ste) aj samotnú aplikáciu:

  • Minifikujte štýly a javascript. Tie sú štandardne docela "ukecané" a ich minifikáciou dokážete ušetriť kopec prenášaných dát
  • Spojte štýly do minimálneho nutného množstva súborov. Rovnako aj skripty. Ak sa dá, ideálne vracajte práve jeden súbor so štýlmi a jeden so skriptami.
  • Optimalizujte obrázky. U JPG obrázkov znížte kompresiu na takú úroveň, aby nedošlo k degradácii obrázku (často sa rieši ručne, aby ste mali dokonalú kontrolu nad výsledkom). PNG obrázky majú bestratovú kompresiu, napriek tomu sa často dajú optimalizovať a zmenšiť tak ich veľkosť bez straty kvality.

V prípade že spojíte súbory (napríklad štýly) do jedného a povolíte ich kešovanie, pri ich zmene si ich prehliadač štandardne nenačíta znova (lebo ich má nakešované). Aby ste prehliadač prinútili si ich znovu stiahnuť, musíte vyvolať zmenu URL (názvu súboru alebo query string).

Na toto sa používa verzovanie. Jedno z riešení, ktoré použijeme aj my je, že po spojení súborov do jedného sa z jeho obsahu vypočíta tzv. checksum, teda nám vznikne akýsi reťazec, ktorý závisí od obsahu súboru. V HTML potom linkujete tento súbor a jeho názov a obsah si prehliadač nakešuje.

Keď vykonáte zmeny a znovu sa spoja súbory do jedného, vypočítaný checksum sa bude od toho predchádzajúceho líšiť. V HTML linkujete na tento nový, ktorý pri prvej návšeteve prehliadač nebude mať ešte nakešovaný a tak si ho stiahne (a zasa nakešuje) a pri ďalšej už bude súbor čítať z keše.

Je to jednoduché a veľmi efektívne riešenie. A dá sa ľahko automatizovať, čo si dnes aj ukážeme.

Distribučná verzia webu

Keďže budeme Gulpom niektoré súbory modifikovať, iné vytvárať, nie je vhodné, aby sme menili naše pôvodné a zdrojové súbory. Preto sa obvykle výstup z automatizácie zapisuje do iného adresára. My použijeme adresár dist, ktorý bude v roote nášho projektu. Vy si však zvoľte názov a jeho polohu tak ako chcete.

V adresári dist budeme mať podobnú štruktúru, ako máme v zdrojovom adresári, teda budeme mať podadresáre js, css a font.

Poďme teda na naše úlohy.

Spojenie súborov

Ako prvé si spojíme linkované súbory. Ak si pozrieme štýly, ktoré linkujeme, uvidíme v HTML toto:

<link rel="stylesheet" href="css/reset.css">
<link rel="stylesheet" href="css/material.css">
<link rel="stylesheet" href="css/weather-icons.css">
<link rel="stylesheet" href="css/weather.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700" type="text/css">

To je spolu 6 súborov štýlov. Posledné dva sú pre webové fonty, tie chceme naďalej načítavať z webu a tak tie necháme tak a budeme spájať iba prvé 4.

Použijeme skvelý npm balíček (modul) gulp-useref, ktorý však od nás vyžaduje drobnú pomoc. Musíme mu dať vedieť ktoré štýly a skripty chceme spojiť a do akého súboru. To sa robí pridaním komentárov do HTML.

Začnime tým, že si nainštalujeme balíček (ako čisto vývojový):

npm install --save-dev gulp-useref

Prejdeme do editora, kde máme otvorený gulpfile.js a v ňom si načítame tento modul, niekde na začiatku skriptu:

var useref = require('gulp-useref');

A vytvoríme si zatiaľ prázdny task s názvom production:

gulp.task('production', function() {
});

Náš task bude pracovať nad hlavným dokumentom. Z neho si načíta, ktoré súbory má spojiť, spojí ich, zapíše ich na disk a potom zapíše na disk upravený HTML dokument, ktorý bude namiesto niekoľkých štýlov a skriptov odkazovať na ich spojené verzie.

Upravme si teda HTML a obaľme štýly a skripty patričnými komentármi. Výstupný skombinovaný súbor chceme aby sa volal weather.css resp. weather.js:

<!-- build:css css/weather.css -->
<link rel="stylesheet" href="css/reset.css">
<link rel="stylesheet" href="css/material.css">
<link rel="stylesheet" href="css/weather-icons.css">
<link rel="stylesheet" href="css/weather.css">
<!-- endbuild -->

A pre skripty (na konci stránky):

<!-- build:js js/weather.js -->
<script src="js/jquery-2.1.4.js"></script>
<script src="js/material.js"></script>
<script src="js/weather.js"></script>
<!-- endbuild -->

Upravme si teda úlohu, začneme jednoducho a postupne budeme pridávať funkcionalitu.

Plugin useref má metódu assets, ktorá keď sa zapojí do pipeline, tak preskenuje vstupné súbory (v našom prípade jediný HTML súbor) a nájde si svoje komentáre.

Vytvorí z nich nový stream, s ktorým ďalej viete pracovať (pridať minifikáciu či verzovanie) a keď ste hotový, zavoláte nad týmto streamom funkciu restore, ktorá vám do hlavnej pipeline vráti pôvodné súbory (naše HTML).

Samotná funkcia useref vám potom HTML zmodifikuje a všetko čo je medzi komentármi vám nahradí odkazom na skombinovaný súbor.

Prvá verzia našej úlohy tak bude vyzerať nasledovne:

gulp.task('production', function() {
  var assetsStream = useref.assets();

  return gulp.src('index.html')
    .pipe(assetsStream)
    .pipe(assetsStream.restore())
    .pipe(useref())
    .pipe(gulp.dest('dist'));
});

Stream, ktorý nám vracia funkcia assets si uložíme do premennej, aby sme nad ním neskôr vedeli zavolať restore.

Čiže, začíname tým, že si načítame náš HTML súbor a dáme ho do pipiline (gulp.src). Hneď v zápätí do pipeline strčíme assets - plugin do neho pridá skombinované súbory. Pridaním restore sa vrátime k pôvodnému HTML a volaním useref sa v ňom nahradí odkaz na skripty a štýly. Výsledok zapíšeme do adresára dist (cez gulp.dest).

Po spustení sa nám toho až tak veľa užitočného nestalo, ale sú to solídne základy, na ktorých môžeme stavať. V adresári dist nám vznikol index.htm čo je skoro kópia nášho pôvodného súboru s tým rozdielom, že v ňom už nájdeme odkazy na kombinované štýly:

<link rel="stylesheet" href="css/weather.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700" type="text/css">

A na kombinované skripty:

<script src="js/weather.js"></script>

Kde sú skombinované súbory? Tam kde majú byť, v patričných podadresároch (css a js). My ich ovšem chceme nie len spojené, ale hlavne minifikované a verzionované. A tie si čochvíľa zabezpečíme.

Minifikácia

Fajn, v istom kroku náš stream obsahuje všetky kombinované skripty a štýly. V našom prípade jeden kombinovaný štýl a jeden skript, ale v reále si ich môžete nechať vygenerovať viacero ak chcete.

To je ten správny moment na ich minifikáciu. Na minifikáciu slúžia rôzne algoritmy a rôzne moduly. Na minifikáciu skriptov použijeme modul gulp-minify-css a na minifikáciu skriptov gulp-uglify.

Treba si však uvedomiť, že v jednom momente máme v streame rôzne typy súborov - skripty a štýly. A na rôzne súbory chceme použiť rôzne pluginy. A presne s týmto nám pomôže modul gulp-if.

Všetky tri si nainštalujeme:

npm install --save-dev gulp-minify-css
npm install --save-dev gulp-uglify
npm install --save-dev gulp-if

A na všetky tri si načítame cez require:

var minify = require('gulp-minify-css');
var uglify = require('gulp-uglify');
var gulpif = require('gulp-if');

A nakoniec si ich zapojíme do pipeline. Kde? Ešte pred volanie restore. Aktuálna verzia tasku tak vyzerá nasledovne:

gulp.task('production', function() {
  var assetsStream = useref.assets();

  return gulp.src('index.html')
    .pipe(assetsStream)
    .pipe(gulpif('*.css', minify())) 
    .pipe(gulpif('*.js', uglify()))
    .pipe(assetsStream.restore())
    .pipe(useref())
    .pipe(gulp.dest('dist'));
});

Do pipeline pridávame dve veci. V prvom kroku použijeme gulp-if na volanie modulu gulp-minify-css, pričom vravíme, nech sa spustí aby na css súbory. V druhom potom gulp-uglify na js súbory.

Po spustení tasku vidíme, že vygenerované súbory (weather.css a weather.js) sú už minifikované. Ale ešte potrebujeme zabezpečiť verzionovanie.

Verzionovanie

Samotné verzionovanie potrebujeme vykonať v dvoch krokoch. Najskôr potrebujeme súbory premenovať, t.j. pridať do nic reťazec vypočítaný na základe obsahu. Na to nám výborne poslúži modul gulp-rev.

Spájanie súborov? Minifikácia? Verzionovanie? Áno, aj im sa venujeme na mojich školeniach JavaScriptu (Node.js, jQuery, AngularJS, ...). Nakódiť web je jedna vec, ale naprogramovať ho tak, aby sa aj rýchlo načítaval, je nemenej dôležité.

To však nestačí. Je síce fajn, že sa názov súboru upraví, ale ešte potrebujeme, aby sa zo samotného HTML dokumentu linkoval ten správny súbor, teda ten s verziou v názve. Na to zase použijeme modul gulp-rev-replace.

Nainštalujeme si obidva:

npm install --save-dev gulp-rev 
npm install --save-dev gulp-rev-replace

A načítame si ich na začiatku skriptu:

var rev = require('gulp-rev');
var revReplace = require('gulp-rev-replace');

A stačí ich zapojiť na správne miesta v pipeline a budeme hotový. Kde ich zapojíme? Každý inde. Prvý ešte pred restore, lebo musí pracovať so spojenými a minifikovanými css a js súbormi. Druhý ale až za restore, lebo ten pracuje práve s HTML súborom.

Naša ďalšia verzia tasku vyzerá nasledovne:

gulp.task('production', function() {
  var assetsStream = useref.assets();

  return gulp.src('index.html')
    .pipe(assetsStream)
    .pipe(gulpif('*.js', uglify()))
    .pipe(gulpif('*.css', minify()))
    .pipe(rev()) 
    .pipe(assetsStream.restore())
    .pipe(useref())
    .pipe(revReplace())
    .pipe(gulp.dest('dist'));
});

No vidíte, ako sa nám to pekne poskladalo. V adresári dist teraz máme html súbor, štýly aj skripty. Ale chýbajú nám tam ešte fonty.

Skopírovanie fontov

Ak sa v tomto momente pozriete do výstupného adresára (dist), tak si všimnete, že nám tam chýba adresár s fontami. Rovnako ak si z tohto adresára otvoríte index.htm v prehliadači tak uvidíte, že na stránke sa nezobrazuje ikonka počasia (pochádzajúca z webového fontu).

Musíme teda do adresára dist dostať ešte fonty a keďže s nimi nechceme robiť žiadnu optimalizáciu ani konverziu, task bude veľmi jednoduchý:

gulp.task('fonts', function() {
  return gulp.src('./font/*')
    .pipe(gulp.dest('dist/font'));
 });

Veľmi jednoduchá úloha, však? Načítame všetky súbory z adresára font a skopírujeme ich do adresára dist.

Nabudúce

Teraz stačí spustiť úlohu fonts a potom production a máme v adresári dist kompletný a funkčný výsledok. Aktuálnu verziu riešenia nájdete v adresári 03 na mojom githube.

Ale ešte nie sme na konci. Nabudúce sa pozrieme na to, ako spúšťať viacero úloh v stanovenom poradí. Takže ostaňte naladení...

Mohlo by ťa tiež zaujímať

Páčil sa ti príspevok?

Zdieľaj príspevok alebo si ho odlož na neskôr

Sleduj ma

Ak nechceš premeškať príspevky ako je tento, sleduj ma na Twitteri, alebo ak máš RSS čítačku, môžeš sledovať môj RSS kanál.