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]; %>' }] } });