Grunt, nebojte se automatizace

Grunt je Node.js modul (NPM), pomocí kterého můžete psát úkoly, které na rozdíl od BATCH a SHELL skriptů jsou nezávislé na systému … a hlavně je můžete psát v Javascriptu.

Nemusíte se ale bát, že se svými skromnými znalostmi jQuery nebudete vědět, jak psát skripty pro Node.JS. Úkolem Gruntu je právě co nejvíce zjednodušit nastavení úkolů, které se tak spíše podobá konfiguraci JSON souboru (i když je to JS a ne JSON).

Instalace Node.JS

Jako první krok budete muset nainstalovat Node.js do počítače, což ale není nijak složité – stačí stáhnout instalátor z nodejs.org (Windows, MacOS) nebo použít připravené příkazy pro stažení balíku (Linux, MacOS Brew, apod.).

Po instalaci stačí spustit konzoli (na Windows 7+ zadejte cmd do Nabídky Start, na MacOS spusťte Terminal) a zadat příkaz:

> npm

Ten ověří, že se Node.js správně nainstaloval (pokud ne, konzole upozorní na neznámý příkaz) a vypíše nápovědu a také cestu, kam se budou instalovat globální balíky.

Instalace Grunt

Jakmile máte funkční Node.js a jeho správce modulů (NPM – Node.js Package Manager), můžete pomocí něj stáhnout Grunt.

Je důležité si uvědomit, jak se moduly instalují: můžete je instalovat globálně, takže pak budou dostupné odkudkoliv z příkazové řádky, lokálně, což se používá k tomu, aby byl modul dostupný pro konkrétní projekt (pak ho musíte instalovat z jeho kořenové složky) a nebo vývojářsky, což je podobné jako lokální instalace, ale nebude se instalovat na produkčním serveru. Poznámka: globálně znamená „pro aktuálního uživatele“. Ostatní uživatelé PC si budou muset NPM a balíky nainstalovat sami pro sebe.

Pro globální instalaci gruntu (nebo jakéhokoliv jiného balíku) musíte přidat parametr -g (global), protože bez něj se balík nainstaluje do aktuální složky (do podsložky node_modules):

> npm install -g grunt-cli

Následně musíte vytvořit ve složce, kde chcete spouštět úkol (nejčastěji složka vašeho projektu), soubor gruntfile.js, do kterého budete zadávat konfigurace úkolů. Aby šli úkoly spouštět, je potřeba ještě nainstalovat Grunt Task Runner (jehož modul se jmenuje jednoduše grunt) do stejné složky, jako gruntfile.js – to je proto, aby update Gruntu v jednom projektu nerozbil úkoly v jiném projektu jen proto, že jsou nekompatibilní s novější verzí.

> cd c:\projects\MyProject
> npm install grunt

Pokud víte, že nebudete chtít spouštět žádné úkoly na produkčním serveru (např. protože tak jednoduše vše nahrajete přes FTP), můžete Grunt nainstalovat jen pro vývojáře, což provedete parametrem --save-dev:

> npm install --save-dev grunt

Úkol (jehož konfoguraci u ukážeme záhy) můžete spustit ze složky, kde je umístěn gruntfile.js příkazem:

> grunt jmeno_ukolu

Nebo pokud má úkol nějaké parametry (opět viz dále):

> grunt jmeno_ukolu:parametry:ukolu --param=1

Pomocí parametrů zadaných přes dvojtečku můžete specifikovat podúkoly a nebo zadat parametry, které pak dostane funkce úkolu jako své parametry – jde tedy o parametry pro konkrétní úkol. Naproti tomu parametry zadané za pomlčky lze získat odkudkoliv příkazem grunt.option('param') – jde tedy o parametr, který může být nezávislý na úkolu a může např. zobrazit nápovědu nebo seznam úkolů v daném projektu.

Příprava úkolu

Pro ukázku si vymyslím modul ukol, který bude dělat neurčité operace se zadanými soubory a budu tak moci ukázat různé možnosti konfigurace, které by pro konkrétní modul nemuseli dávat smysl. Dále si pak ukážeme nastavení pro užitečné moduly jako je minifikace CSS, kompilace JS, optimalizace obrázků, atd.

Nejprve musíte do NPM nainstalovat modul, který budete pomocí Gruntu spouštět. Obvykle mají předponu grunt-contrib-*, ale není to podmínka, takže modul ukol by měl být pojmenován grunt-contrib-ukol, ale může to být jen grunt-ukol nebo ukol. Příslušný příkaz by měl být uveden v nápovědě daného modulu v sekci Instalation:

> npm install --save-dev grunt-contrib-ukol

Další, co musíte udělat, je přidat do gruntfile.js příkaz, který daný modul načte, abyste z něj mohli spouštět úkoly. Tento příkaz by měl být také uveden v nápovědě modulu pod Instalation, ale jde o jednoduché zavolání metody load se jménem modulu:

grunt.loadNpmTasks('grunt-contrib-ukol');

Celý gruntfile.js by teď měl obsahovat následující:

module.exports = function (grunt) {
    grunt.initConfig({});
    grunt.loadNpmTasks('grunt-contrib-ukol')
};

Nyní přejdeme k tomu, že přidáme konfiguraci pro náš ukol do parametru metody grunt.initConfig(). Každý modul má nějaké klíčkové slovo, které musí být opět uvedeno v jeho nápovědě, obvykle včetně celé výchozí nebo ukázkové konfigurace. Pro modul ukol bude toto slovo ukol, takže do parametru (objektu) zadáme klíč ukol:

grunt.initConfig({
    ukol: {}
});

Konfigurační objekt ukol pak může obsahovat objekt options, který definuje nastavení úkolu a pole files, které určuje, které soubory bude zpracovávat. Většina modulů také může obsahovat podúkoly, které se definují tak, že do konfiguračního objektu uvedete libovolné jméno (splňující JS podmínky pro klíč objektu):

ukol: {
    options: {},
    files: [],
    ukol1: {},
    ukol2: {}
}

Každý podúkol pak může obsahovat vlastní options a files. Pokud úkol nemá options nebo files určeno, použije se konfigurace celého úkolu (některé moduly to ale nemusí podporovat!).

Celý úkol s použitím ukol.files spustíte příkazem:

> grunt ukol

Pro zavolání podúkolu s použitím jeho vlastního seznamu ukol.ukol1.files přidáte jeho jméno:

> grunt ukol:ukol1

Co obsahuje objekt options záleží čistě na modulu a mělo by to být popsáno v jeho nápovědě. Naproti tomu pole files je určené specifikací Gruntu a je společné pro všechny moduly.

Určení souborů pro úkol

V úplně nejjednodušší verzi můžete místo pole použít objekt, který může mít buď klíče src a dest:

files: { src: '*', dest: 'processed/*'}

nebo dvojice klíčů ve formátu dest: src:

files: {
    'compiled.js': '*.js',
    'minified.css': ['layout.css', 'theme.css']
}

Ve většině případů si u složitějších projektů s tímto nevystačíte, takže je lepší používat syntaxi s polem. To obsahuje objekty, kde každý objekt definuje skupinu souborů, které chcete zpracovat:

files: [
    {
        src: '*.*',
        dest: '/processed/*.*'
    }
]

Jako src (zdrojové soubory) a dest (kam se uloží výsledek) můžete použít řetězec se jménem souboru ('layout.css') nebo maskou ('*.css') nebo pole se seznamem konkrétních souborů nebo masek (['main.js', '*.css']).

Všechna jména souborů jsou relativní k umístění souboru gruntfile.js, přičemž cesta začíná jménem souboru nebo složky (neuvádí se tedy ani tečka ‚.‚ ani lomítko ‚/‚ nebo ‚\‚ na začátku).

Maska otazník ? zastupuje jeden libovolný znak, hvězdička * zastupuje cokoliv (‚*.js‘ tedy znamená všechny JS soubory ve složce s gruntfile.js – včetně jeho samotného). Samotná hvězdička pak znamená libovolný soubor nebo složku v dané složce.

Pokud chcete zpracovat soubory ve všech podsložkách nezávisle na stupni zanoření, musíte místo jedné hvězdičky použít dvě:

files: [
    {
        src: ['**/*.js', 'img/**/*.jpg'],
        dest: 'processed/*.*'
    }
]

Důležité je pamatovat si, že 'js/*.js' zpracuje všechny soubory POUZE ve složce /js/. Pokud chcete zpracovat i soubory v podsložkách, musíte použít 'js/**/*.js'. Díky expand se /**/ automaticky nahradí tak, aby hledal i 'js/*.js'.

Pokud chcete nějaké soubory naopak vyloučit – např. zmíněný gruntfile.js, který většinou nechcete zpracovávat, stačí použít pole a před jméno zadat vykřičník. Pole souborů se pak vyhodnocuje zleva doprava a přidává nebo odebírá soubory z výsledného seznamu:

files: [
    {
        src: [ '*.js',    //všechny skripty
               '!*.min.js', //kromě minifikov.
               '!gruntfile.js' //kromě cfg
        ],
        dest: 'processed/*.*'
    }
]

A pokud chcete zpracovat různé typy souborů a nechce se vám pro každý typ opakovat celou masku, můžete použít seznam:

files: [
    {
        src: '**/*.{js,css,png,jpg}',
        dest: 'processed/*.*'
    }
]

Rozšířené možnosti hledání souborů

Pokud do objektu souborů zadáte klíč expand:true, budete moci specifikovat zdrojové a cílové soubory po částech a Grunt pak správně najde všechny soubory ve všech složkách.

Klíč cwd (current working directory) určuje, v jaké složce se mají soubory hledat. Všechny ostatní definice (src, dest, atd.) pak budou relativní k této složce. Pokud uvedete matchBase:true, budou se hledat soubory pouze v cwd, ale jen ty, které mají v masce jméno dané složky:

files: [{
    cwd: 'www/*/', // ve všech podsložkách www
                   //ale ne přímo ve www
    matchBase: true,
    src: '{js|css}/**/*.js' // pouze složky
                            // www/js a www/css
}]

Díky expand:true může dest obsahovat pouze složku, do které se budou soubory ukládat. Jméno souboru se pak určí podle vstupního souboru a dalších nastavení. Pokud chcete, aby se do cílové složky uložili všechny soubory nezávisle na tom, v jaké podsložce byly umístěny, přidejte flatten:true. Pozor ale, že pokud dvě různé složky budou obsahovat stejně pojmenovaný soubor (např. 'css/layout/main.css' a 'css/theme/main.css'), první soubor se přepíše tím později nalezeným.

Klíč ext určuje příponu cílového souboru. Např. ext:'.min.css' přidá příponu minifikovaným CSS. Pokud máte soubory, jejichž jména obsahují více teček (např. jquery.2.0.0.js), je potřeba ještě přidat klíč extDot:'last', protože jinak by se za příponu považovalo vše za první tečkou (jquery.2.0.0.js vytvoří jquery.min.js; po použití extDot:'last' vytvoří správně jquery.2.0.0.min.js).

files: [
    {
        expand: true,
        src: '**/*.js',
        dest: 'js/__compiled',
        flatten: true, //uloží bez podsložek
        ext: '*.min.js',
        extDot: 'last'
    }
]

Určení souborů skriptem

Pro složitější určení jména výstupního souboru můžete použít klíč rename, který definuje funkci, která jako vstupní parametr dostane jméno výstupní složky (klíč dest) a jméno vstupního souboru a musí vrátit jméno a cestu cílového souboru:

files: [
    {
        expand: true,
        src: '**/*.js',
        dest: 'js/__compiled',
        flatten: true,
        /* uloží main.js do kořenové složky
           projektu, ostatní do DEST */
        rename: function(dest, file) {
            if (file === 'main.js') {
                return 'main.min.js';
            }
            return dest 
              + file.replace(/js$/, 'min.js');
        }
    }
]

Pokud je současně uvedeno flatten:true, bude parametr file obsahovat pouze jméno souboru (např. 'main.js'). V opačném případě bude obsahovat celou zdrojovou cestu (např. 'www/js/core/main.js'). Pokud flatten:true najde dva stejně se jmenující souboru, zavolá funkci rename dvakrát, ale ta nebude již schopna určit, který soubor byl odkud načten. Maximálně může zajistit, že se pojmenují tak, aby se nepřepsali (např. 'main.min.css' a 'main-01.min.css').

Pro složitější omezení vstupních souborů můžete použít klíč filter, který, stejně jako rename, určuje funkci. Na rozdíl od rename ale filter dostane jen jméno vstupního souboru a musí vrátit TRUE (soubor se zpracuje) nebo FALSE (soubor se přeskočí):

files: [
    {
        expand: true,
        src: '**/*',
        dest: 'processed',
        /* přeskočí skryté (Linux) soubory */
        filter: function(file) {
            return '.' !== file[0];
        }
     }
]

Poznámka: stejného efektu dosáhnete použitím klíče dot:true (hledá i soubory začínající tečkou) nebo dot:false (přeskočí soubory začínající tečkou, což je i výchozí chování).

Jako funkci filter můžete použít i funkce pro práci se soubory z fs.stats tím, že uvedete jen jejich jméno:

files: [{
    src: '*',
    filter: 'isFile' //přeskočí složky a symlinky
}]

Jméno souboru (nebo jeho část, třeba cwd nebo ext) může obsahovat tzv. template, což je obdoba tagu <?php ?> v HTML. Pro Grunt se tyto skripty definují pomocí <% %> a mohou obsahovat libovolný javascriptový kód (výsledek posledního příkazu se pak vloží na místo template):

files: [{ 
    src: '**/<% ['*.js', '*.inc']; %>,
    dest: '**/<% '*' + '.js'; %>'
}]

Pokud skript vrátí pole, Grunt ho automaticky zpracuje v cyklu a najde všechny soubory, které odpovídají jednotlivým definicím.

Uvnitř skriptu můžete používat proměnné definované v konfiguračním objektu z grunt.initConfig({}). Pokud tedy chcete třeba použít proměnnou z ukoly.options, stačí použít:

ukol: {
    options: { process: true },
    files: [{ src: '<%
       if (ukol.options.process) 
       { '*.js'; } else { '' } %>'}]
}

Pokud jen chcete vypsat hodnotu proměnné z konfigurace, můžete použít (čistě pro přehlednost) template <%= promenna %>:

ukol: {
    options: { files: '*.js' }
    files: [{ src: '<%= ukol.options.files %>' }]
}

Tyto template můžete použít i v options, díky čemuž můžete třeba sdílet nastavení mezi podúkoly nebo dělat závislosti:

ukol: {
    options: { task1: true, task2: false },
    ukol1: {
        options: { 
  task1: <%= ukol.options.task1 %>',
  task2: <%= !ukol.ukol1.options.task1 %>'
        }
}

V templatech můžete také používat data z externích JSON souborů, např.:

ukol: {
    options: grunt.file.readJSON('ukol.json'),
    ukol1: {
        options: { 
            enabled: <%= ukol.options.enabled %>'
        }
    }
}

Samozřejmě můžete sdílet definice i mezi úkoly, jen dejte pozor, abyste se nepomíchali jména úkolů a konfigurací:

grunt.initConfig({
    scripts: 'js/*.js',
    styles: 'css/**/*.css',
    ukol: {
        files: [{
            src: '<% [scripts, styles]; %>'
        }]
    }
});

Napsat komentář

Vaše emailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *

Tato stránka používá Akismet k omezení spamu. Podívejte se, jak vaše data z komentářů zpracováváme..