Automatizace s Gruntem

1 JavaScript, Windows, Linux, CSS

Grunt je skvělý nástroj pro automatizaci opakujících se úkolů. S obrovským množstvím balíčků, které nabízí, si můžete při vývoji odpustit hodně práce. V článku si ukážeme na příkladu jak Grunt využít.

Automatizace s Gruntem

Tento článek patří do seriálu Zrychlení webu a dev stack. Ostatní články seriálu:

  1. Cachujeme, GZipujeme, zrychlujeme
  2. SVG a PNG Sprite, zrychlujeme podruhé
  3. Minifikace - zrychlujeme po třetí
  4. DataURI - zrychlujeme po čtvrté
  5. Webpack lusknutím prstu - Laravel Mix - Alternativa ke Gruntu a Gulpu
  6. Fiddler - web debugger
  7. Nastavení GZip komprese u souborů s fonty
  8. Automatizace s Gruntem - Automatizace ostatních technik ze seriálu
  9. Systém SVG ikon s Gulpem

Grunt i všechny jeho balíčky jsou napsané v NodeJS, takže je asi jasné, že si jej musíme nainstalovat také. Naštěstí je stejně jako většina moderních jazyků multiplatformní, a funguje pod Windows i Linuxem. Když máme NodeJS, je zapotřebí Node package manager, zkráceně NPM (I když možná ne...). Ten někdy je součástí instalace NodeJS. Poté si můžeme hledat pluginy, a samozřejmě nainstalujeme Grunt.

Grunt lze velmi výhodně využít k automatizaci technik pro zrychlení načítání, o kterých jsem psal v předchozích článcích.

package.json

Pokud si najdeme balíček, většinou obsahuje příkaz k instalaci podobný tomu níže. Přepínač --save-dev totiž vloží informace o aktuálním balíčku do souboru package.json, díky čemuž do GITu nemusíme nahrávat všechny balíčky, které mohou mít stovky MB.

# Nainstaluje balíček grunt-contrib-clean a vloží do package.json
npm install grunt-contrib-clean --save-dev

# Nainstaluje všechny balíčky zmíněné v package.json
npm install

Stačí na jiném počítači spustit npm install a všechny balíčky se nainstalují také. Soubor package.json můžete vytvořit ručně a vložit základní kostru, jako je ukázáno níže. Nebo stačí zadat npm init, který soubor vytvoří za vás. Někdy stačí pouze vložit { }

{
  "name": "blog",
  "devDependencies": {  }
}

Balíčky a jejich nastavení

Další soubor, který vytvoříme se jmenuje Gruntfile.js a vložíme jej do hlavní složky projektu. Výhodou konfiguračního souboru je, že je nečekaně napsán v JavaScriptu, takže si v něm můžeme libovolně volat funkce a psát skripty. Celý můj Gruntfile najdete níže a obsahuje plno balíčků pro kompilaci React JSX syntaxe do JavaScriptu, Sassu do CSS, spojení CSS a JS souborů, build a spuštění serveru v Golangu a také automatickou synchronizaci prohlížeče. To vše automaticky po změně souborů. Gruntem se ale také dají automatizovat testy a mnohé další.

Soubor není krátký, ale zato jsem komentáře vložil přímo do něj. Celý soubor můžete také stáhnout, pokud se lépe bude číst v editoru.

module.exports = function(grunt) {

    // Vlastní funkce, kterou si zavoláme níže v tasku concat
    this.getConcatArray = function(dist) {        
        // POZOR
        // Minifikovaný React je produkční verze, neminifikovaná je vývojová verze
        // Vývojová verze obsahuje dodatečné funkce pro kontrolu, které zpomalují běh a v provozu by neměly být.
        var useMinfiedPlugins = false;
        var concatFiles = [
            // concat spojuje soubory v abecedním pořadí
            // Zde si prioritizuji nějaké soubory, a určím pro ně pevné pořadí
            '<%= wwwAssets %>/js/src/plugins/**/*.js',
            // Prvně vložíme všechny zkomilované JSX soubory až poté pluginy a další
            '<%= wwwAssets %>/js/src/_*._jsx.js', // soubory začínající _ mají přednost
            '<%= wwwAssets %>/js/src/stores/*._jsx.js', // Poté ze složky store
            '<%= wwwAssets %>/js/src/components/*._jsx.js', // Dále ze složky components
            '<%= wwwAssets %>/js/src/*._jsx.js', // Až poté všechny ostatní ve složce src končící na ._jsx.js
            '<%= wwwAssets %>/js/src/main._jsx.js', // Hlavní JSX soubor až poslední
            '<%= wwwAssets %>/js/src/*.js', // Pak všechny ostatní JS soubory, již vložené dříve se znova nevloží
        ];

        if (useMinfiedPlugins || dist == true) {
            // Unshift vloží na začátek pole. React musí být vždy první
            // Druhý řádek, negace značí, že soubor react.js se vložit nesmí, protože se vkládá react.min.js
            concatFiles.unshift('<%= wwwAssets %>/js/src/plugins/react.min.js');
            concatFiles.push('!<%= wwwAssets %>/js/src/plugins/react.js');
        }else{
            concatFiles.unshift('<%= wwwAssets %>/js/src/plugins/react.js');
            concatFiles.push('!<%= wwwAssets %>/js/src/plugins/react.min.js');
        }
        return concatFiles;
    }
    
    // Samotná konfigurace Gruntu
    grunt.initConfig({
        wwwAssets: 'www', // Pouze proměnná, je použita mnohokrát níže
 
        // Task compass převede SASS s frameworkem Compass do CSS
        // Celý task má 2 cíle, dist a debug které můžou být spuštěny podle potřeby
        // Nastavení balíčků najdete vždy u každého z nich v dokumentaci
        compass: {
            dist: {
                options: {
                    config: 'config.rb',
                    sourcemap: true,
                    force: true
                }
            },
            debug: {
                options: {
                    config: 'config.rb',
                    sourcemap: false,
                    outputStyle: 'expanded'
                }
            }
        },
        // PostCSS je CSS postprocessor, obsahuje množství pluginů
        // Já zde použil pouze Autoprefixer
        postcss: {
            options: {
                // map: true, // inline sourcemaps
                processors: [
                    require('autoprefixer')({browsers: '>1%'}), // add vendor prefixes
                ]
            },
            dist: {
                src: '<%= wwwAssets %>/css/styles.css',
                dest: '<%= wwwAssets %>/css/styles.css'
            }
        },
        // Balíček pro sledování změn v souborech
        // pokud se specifikovaný soubor změní, spustí jednotlivé tasky
        watch: {
            sass: {
                files: '<%= wwwAssets %>/css/src/*.scss',
                tasks: ['compass:debug', 'postcss']
            },
            react: {
                // Jakýkoli soubor co má příponu JSX a nachází se ve složce www/js/jsx/ nebo libovolné podsložce
                files: '<%= wwwAssets %>/js/jsx/**/*.jsx',
                // Spustí task clean s cilem react, babel a target debug v tasku concat
                tasks: ['clean:react', 'babel', 'concat:debug']
            },
            js: {
                // Jakýkoli *.js soubor v www/js/src/, ale nesmi končit na *._jsx.js
                // Tento soubor může být změnen v targetu react a provedl by se zbytečně 2x
                files: ['<%= wwwAssets %>/js/src/*.js', '!<%= wwwAssets %>/js/src/*._jsx.js'],
                tasks: ['concat:debug']
            }
        },
        // Vymaže všechny soubory končící na ._jsx.js ve složce www/js/src/ a všech podsložkách
        clean: {
            react: ['<%= wwwAssets %>/js/src/**/*._jsx.js']
        },
        // Provede spojení daných souborů v 1 jediný
        concat: {
            options: {
                separator: '\n',
                process: function(src, filepath) {
                return '\n// Source: ' + filepath + '\n' + src;
                },
            },
            // target debug a dist, oba volají funkci definovanou nahoře která vrátí pole souborů, které spojit a v jakém pořadí
            debug: {
                src: this.getConcatArray(false),
                dest: '<%= wwwAssets %>/js/scripts.js'
            },
            dist: {
                src: this.getConcatArray(true),
                dest: '<%= wwwAssets %>/js/scripts.js'
            }
        },
        // Kompilace JSX syntaxe a ES2015 do běžného JavaScriptu
        babel: {
            // https://php.quicoto.com/use-babel-to-compile-react-jsx-with-grunt/
            options: {
                presets: ['stage-0', 'es2015', 'react']
            },
            jsx: {
                files: [{
                    // Zde se soubory ve složce www/js/jsx/ a podsložek přeloží a
                    // uloží do složky www/js/src/ ale složky zůstanou zachovány
                    // Příklad: www/js/jsx/main.jsx -> www/js/src/main._jsx.js
                    // Nebo: www/js/jsx/stores/articles.jsx -> www/js/src/stores/articles._jsx.js
                    expand: true,
                    cwd: '<%= wwwAssets %>/js/jsx/',
                    src: ['**/*.jsx'],
                    dest: '<%= wwwAssets %>/js/src/',
                    ext: '._jsx.js'
                }]
            }
        },
        // Task který kontroluje soubory a pokud se změní, provede refresh prohlížeče
        browserSync: {
            dev: {
                // Které soubory sledovat, chci pouze výsledné, které se uloží až
                // Compass zkompiluje SASS soubory do finálního CSS
                // Babel a Concat přeloží a spojí všechny JS soubory
                bsFiles: {
                    src : [
                        '<%= wwwAssets %>/css/styles.css',
                        '<%= wwwAssets %>/js/scripts.js'
                    ]
                },
                options: {
                    // Můj server běží na této adrese
                    // browserSync dokáže i server vytvořit a servírovat HTML soubory
                    proxy: 'https://localhost:1443',
                    watchTask: true
                }
            }
        },
        // Run slouží ke spuštění příkazů jako v příkazové řádce
        // Server se kompiluje a spustí pomocí Makefile a task run spustí tento soubor
        // Až se v příkazové řádce objeví "./blog" tak se tváří jako vyřízený a pokračují další tasky
        run: {
            options: {
                ready: new RegExp("\./blog"),
                wait: false,
            },
            GoServer: {
                cmd: 'make',
                args: [
                    'run'
                ]
            }
        }
    });
 
    // Musíme načíst jednotlivé balíčky, které chceme používat
    grunt.loadNpmTasks('grunt-contrib-compass');
    grunt.loadNpmTasks('grunt-babel');
    grunt.loadNpmTasks('grunt-postcss');
    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-contrib-concat');
    grunt.loadNpmTasks('grunt-contrib-clean');
    grunt.loadNpmTasks('grunt-browser-sync');
    grunt.loadNpmTasks('grunt-run');

    // Registrujeme vlastní tasky a specifikujeme, co daný task zastupuje
    // Můžete si všimnout, že tasky se dají skládat, default task spustí také můj task build-debug
    // Každý task se spustí jako task:target, přičem záleží na balíčku co udělají, pokud target není specifikovaný
    // Některý spustí pouze 1, některé jako watch všechny 
    grunt.registerTask('default', ['build-debug', 'run', 'browserSync', 'watch']);
    grunt.registerTask('build', ['clean', 'compass:dist', 'postcss', 'babel', 'concat:dist']);
    grunt.registerTask('build-debug', ['clean', 'compass:debug', 'postcss', 'babel', 'concat:debug']);
 
};

Nyní můžeme pomocí příkazů spouštět jednotlivé tasky ať už vlastní, nebo definované. Pokud však žádný neuvedeme, spustí se default. V případě Gruntu je potřeba se přepnout pomocí příkazu cd do složky daného projektu.

# Spustí default task
grunt
# Spustí build-debug, což zahrnuje clean, compass:debug, postcss...
grunt build-debug
# Spustí pouze task clean
grunt clean

K tomuto článku již není možné přidávat další komentáře

Komentáře

Zdravím a děkuji za dobře napsaný článek. S gruntem se teprve seznamuji a každá ukázka použití se hodí :)